Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
A
Actor Framework
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Metrics
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
cpp-libs
Actor Framework
Commits
04e02412
Commit
04e02412
authored
Apr 16, 2022
by
Dominik Charousset
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Make most operators *cold* by default
parent
a52cde49
Changes
53
Hide whitespace changes
Inline
Side-by-side
Showing
53 changed files
with
4382 additions
and
3454 deletions
+4382
-3454
libcaf_core/CMakeLists.txt
libcaf_core/CMakeLists.txt
+6
-2
libcaf_core/caf/detail/ref_counted_base.hpp
libcaf_core/caf/detail/ref_counted_base.hpp
+45
-0
libcaf_core/caf/detail/type_traits.hpp
libcaf_core/caf/detail/type_traits.hpp
+3
-0
libcaf_core/caf/disposable.hpp
libcaf_core/caf/disposable.hpp
+3
-2
libcaf_core/caf/flow/concat.hpp
libcaf_core/caf/flow/concat.hpp
+1
-18
libcaf_core/caf/flow/coordinated.hpp
libcaf_core/caf/flow/coordinated.hpp
+36
-0
libcaf_core/caf/flow/coordinator.hpp
libcaf_core/caf/flow/coordinator.hpp
+0
-5
libcaf_core/caf/flow/fwd.hpp
libcaf_core/caf/flow/fwd.hpp
+0
-3
libcaf_core/caf/flow/item_publisher.hpp
libcaf_core/caf/flow/item_publisher.hpp
+93
-0
libcaf_core/caf/flow/merge.hpp
libcaf_core/caf/flow/merge.hpp
+0
-12
libcaf_core/caf/flow/observable.hpp
libcaf_core/caf/flow/observable.hpp
+288
-2085
libcaf_core/caf/flow/observable_builder.hpp
libcaf_core/caf/flow/observable_builder.hpp
+163
-146
libcaf_core/caf/flow/observable_decl.hpp
libcaf_core/caf/flow/observable_decl.hpp
+244
-0
libcaf_core/caf/flow/observer.hpp
libcaf_core/caf/flow/observer.hpp
+155
-127
libcaf_core/caf/flow/op/README.md
libcaf_core/caf/flow/op/README.md
+2
-0
libcaf_core/caf/flow/op/base.hpp
libcaf_core/caf/flow/op/base.hpp
+27
-0
libcaf_core/caf/flow/op/cell.hpp
libcaf_core/caf/flow/op/cell.hpp
+167
-0
libcaf_core/caf/flow/op/cold.hpp
libcaf_core/caf/flow/op/cold.hpp
+50
-0
libcaf_core/caf/flow/op/concat.hpp
libcaf_core/caf/flow/op/concat.hpp
+261
-0
libcaf_core/caf/flow/op/defer.hpp
libcaf_core/caf/flow/op/defer.hpp
+50
-0
libcaf_core/caf/flow/op/empty.hpp
libcaf_core/caf/flow/op/empty.hpp
+75
-0
libcaf_core/caf/flow/op/fail.hpp
libcaf_core/caf/flow/op/fail.hpp
+39
-0
libcaf_core/caf/flow/op/from_generator.hpp
libcaf_core/caf/flow/op/from_generator.hpp
+159
-0
libcaf_core/caf/flow/op/from_resource.hpp
libcaf_core/caf/flow/op/from_resource.hpp
+225
-0
libcaf_core/caf/flow/op/from_steps.hpp
libcaf_core/caf/flow/op/from_steps.hpp
+295
-0
libcaf_core/caf/flow/op/hot.hpp
libcaf_core/caf/flow/op/hot.hpp
+50
-0
libcaf_core/caf/flow/op/interval.hpp
libcaf_core/caf/flow/op/interval.hpp
+39
-0
libcaf_core/caf/flow/op/mcast.hpp
libcaf_core/caf/flow/op/mcast.hpp
+256
-0
libcaf_core/caf/flow/op/merge.hpp
libcaf_core/caf/flow/op/merge.hpp
+331
-0
libcaf_core/caf/flow/op/never.hpp
libcaf_core/caf/flow/op/never.hpp
+73
-0
libcaf_core/caf/flow/op/prefix_and_tail.hpp
libcaf_core/caf/flow/op/prefix_and_tail.hpp
+291
-0
libcaf_core/caf/flow/op/zip_with.hpp
libcaf_core/caf/flow/op/zip_with.hpp
+272
-0
libcaf_core/caf/flow/single.hpp
libcaf_core/caf/flow/single.hpp
+11
-129
libcaf_core/caf/flow/step.hpp
libcaf_core/caf/flow/step.hpp
+130
-319
libcaf_core/caf/flow/subscription.hpp
libcaf_core/caf/flow/subscription.hpp
+22
-34
libcaf_core/caf/ref_counted.hpp
libcaf_core/caf/ref_counted.hpp
+5
-25
libcaf_core/caf/scheduled_actor/flow.hpp
libcaf_core/caf/scheduled_actor/flow.hpp
+7
-8
libcaf_core/src/detail/ref_counted_base.cpp
libcaf_core/src/detail/ref_counted_base.cpp
+31
-0
libcaf_core/src/flow/coordinated.cpp
libcaf_core/src/flow/coordinated.cpp
+13
-0
libcaf_core/src/flow/observable_builder.cpp
libcaf_core/src/flow/observable_builder.cpp
+1
-130
libcaf_core/src/flow/op/interval.cpp
libcaf_core/src/flow/op/interval.cpp
+163
-0
libcaf_core/src/flow/subscription.cpp
libcaf_core/src/flow/subscription.cpp
+7
-27
libcaf_core/src/ref_counted.cpp
libcaf_core/src/ref_counted.cpp
+0
-18
libcaf_core/test/flow/concat.cpp
libcaf_core/test/flow/concat.cpp
+3
-96
libcaf_core/test/flow/concat_map.cpp
libcaf_core/test/flow/concat_map.cpp
+2
-2
libcaf_core/test/flow/fail.cpp
libcaf_core/test/flow/fail.cpp
+5
-5
libcaf_core/test/flow/item_publisher.cpp
libcaf_core/test/flow/item_publisher.cpp
+51
-0
libcaf_core/test/flow/merge.cpp
libcaf_core/test/flow/merge.cpp
+58
-87
libcaf_core/test/flow/mixed.cpp
libcaf_core/test/flow/mixed.cpp
+72
-0
libcaf_core/test/flow/never.cpp
libcaf_core/test/flow/never.cpp
+16
-22
libcaf_core/test/flow/prefix_and_tail.cpp
libcaf_core/test/flow/prefix_and_tail.cpp
+55
-84
libcaf_core/test/flow/single.cpp
libcaf_core/test/flow/single.cpp
+7
-4
libcaf_core/test/flow/zip_with.cpp
libcaf_core/test/flow/zip_with.cpp
+24
-64
No files found.
libcaf_core/CMakeLists.txt
View file @
04e02412
...
@@ -129,6 +129,7 @@ caf_add_component(
...
@@ -129,6 +129,7 @@ caf_add_component(
src/detail/print.cpp
src/detail/print.cpp
src/detail/private_thread.cpp
src/detail/private_thread.cpp
src/detail/private_thread_pool.cpp
src/detail/private_thread_pool.cpp
src/detail/ref_counted_base.cpp
src/detail/ripemd_160.cpp
src/detail/ripemd_160.cpp
src/detail/serialized_size.cpp
src/detail/serialized_size.cpp
src/detail/set_thread_name.cpp
src/detail/set_thread_name.cpp
...
@@ -147,8 +148,10 @@ caf_add_component(
...
@@ -147,8 +148,10 @@ caf_add_component(
src/error.cpp
src/error.cpp
src/event_based_actor.cpp
src/event_based_actor.cpp
src/execution_unit.cpp
src/execution_unit.cpp
src/flow/coordinated.cpp
src/flow/coordinator.cpp
src/flow/coordinator.cpp
src/flow/observable_builder.cpp
src/flow/observable_builder.cpp
src/flow/op/interval.cpp
src/flow/scoped_coordinator.cpp
src/flow/scoped_coordinator.cpp
src/flow/subscription.cpp
src/flow/subscription.cpp
src/forwarding_actor_proxy.cpp
src/forwarding_actor_proxy.cpp
...
@@ -285,17 +288,18 @@ caf_add_component(
...
@@ -285,17 +288,18 @@ caf_add_component(
dynamic_spawn
dynamic_spawn
error
error
expected
expected
flow.broadcaster
flow.concat
flow.concat
flow.concat_map
flow.concat_map
flow.defer
flow.defer
flow.empty
flow.empty
flow.
error
flow.
fail
flow.flat_map
flow.flat_map
flow.for_each
flow.for_each
flow.generation
flow.generation
flow.interval
flow.interval
flow.item_publisher
flow.merge
flow.merge
flow.mixed
flow.never
flow.never
flow.observe_on
flow.observe_on
flow.prefix_and_tail
flow.prefix_and_tail
...
...
libcaf_core/caf/detail/ref_counted_base.hpp
0 → 100644
View file @
04e02412
// 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 <atomic>
#include <cstddef>
#include "caf/detail/core_export.hpp"
namespace
caf
::
detail
{
// Base class for reference counted objects with an atomic reference count.
class
CAF_CORE_EXPORT
ref_counted_base
{
public:
virtual
~
ref_counted_base
();
ref_counted_base
();
ref_counted_base
(
const
ref_counted_base
&
);
ref_counted_base
&
operator
=
(
const
ref_counted_base
&
);
/// Increases reference count by one.
void
ref
()
const
noexcept
{
rc_
.
fetch_add
(
1
,
std
::
memory_order_relaxed
);
}
/// Decreases reference count by one and calls `request_deletion`
/// when it drops to zero.
void
deref
()
const
noexcept
;
/// Queries whether there is exactly one reference.
bool
unique
()
const
noexcept
{
return
rc_
==
1
;
}
size_t
get_reference_count
()
const
noexcept
{
return
rc_
.
load
();
}
protected:
mutable
std
::
atomic
<
size_t
>
rc_
;
};
}
// namespace caf::detail
libcaf_core/caf/detail/type_traits.hpp
View file @
04e02412
...
@@ -222,6 +222,9 @@ public:
...
@@ -222,6 +222,9 @@ public:
template
<
class
T
>
template
<
class
T
>
constexpr
bool
is_iterable
<
T
>::
value
;
constexpr
bool
is_iterable
<
T
>::
value
;
template
<
class
T
>
constexpr
bool
is_iterable_v
=
is_iterable
<
T
>::
value
;
/// Checks whether `T` is a non-const reference.
/// Checks whether `T` is a non-const reference.
template
<
class
T
>
template
<
class
T
>
struct
is_mutable_ref
:
std
::
false_type
{
};
struct
is_mutable_ref
:
std
::
false_type
{
};
...
...
libcaf_core/caf/disposable.hpp
View file @
04e02412
...
@@ -35,13 +35,13 @@ public:
...
@@ -35,13 +35,13 @@ public:
virtual
void
deref_disposable
()
const
noexcept
=
0
;
virtual
void
deref_disposable
()
const
noexcept
=
0
;
};
};
// -- constructors, destructors, and assignment operators --------------------
explicit
disposable
(
intrusive_ptr
<
impl
>
pimpl
)
noexcept
explicit
disposable
(
intrusive_ptr
<
impl
>
pimpl
)
noexcept
:
pimpl_
(
std
::
move
(
pimpl
))
{
:
pimpl_
(
std
::
move
(
pimpl
))
{
// nop
// nop
}
}
// -- constructors, destructors, and assignment operators --------------------
disposable
()
noexcept
=
default
;
disposable
()
noexcept
=
default
;
disposable
(
disposable
&&
)
noexcept
=
default
;
disposable
(
disposable
&&
)
noexcept
=
default
;
...
@@ -57,6 +57,7 @@ public:
...
@@ -57,6 +57,7 @@ public:
return
*
this
;
return
*
this
;
}
}
// -- factories --------------------------------------------------------------
// -- factories --------------------------------------------------------------
/// Combines multiple disposables into a single disposable. The new disposable
/// Combines multiple disposables into a single disposable. The new disposable
...
...
libcaf_core/caf/flow/concat.hpp
View file @
04e02412
...
@@ -7,21 +7,4 @@
...
@@ -7,21 +7,4 @@
#include "caf/flow/observable.hpp"
#include "caf/flow/observable.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/observer.hpp"
namespace
caf
::
flow
{
namespace
caf
::
flow
{}
// namespace caf::flow
/// Combines the items emitted from `pub` and `pubs...` to appear as a single
/// stream of items.
template
<
class
Observable
,
class
...
Observables
>
observable
<
typename
Observable
::
output_type
>
concat
(
Observable
x
,
Observables
...
xs
)
{
using
output_type
=
output_type_t
<
Observable
>
;
static_assert
(
(
std
::
is_same_v
<
output_type
,
output_type_t
<
Observables
>>
&&
...));
auto
hdl
=
std
::
move
(
x
).
as_observable
();
auto
ptr
=
make_counted
<
concat_impl
<
output_type
>>
(
hdl
.
ptr
()
->
ctx
());
ptr
->
add
(
std
::
move
(
hdl
));
(
ptr
->
add
(
std
::
move
(
xs
).
as_observable
()),
...);
return
observable
<
output_type
>
{
std
::
move
(
ptr
)};
}
}
// namespace caf::flow
libcaf_core/caf/flow/coordinated.hpp
0 → 100644
View file @
04e02412
// 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/core_export.hpp"
namespace
caf
::
flow
{
/// An object that lives on a @ref coordinator.
class
CAF_CORE_EXPORT
coordinated
{
public:
// -- constructors, destructors, and assignment operators --------------------
virtual
~
coordinated
();
// -- reference counting -----------------------------------------------------
/// Increases the reference count of the coordinated.
virtual
void
ref_coordinated
()
const
noexcept
=
0
;
/// Decreases the reference count of the coordinated and destroys the object
/// if necessary.
virtual
void
deref_coordinated
()
const
noexcept
=
0
;
friend
void
intrusive_ptr_add_ref
(
const
coordinated
*
ptr
)
noexcept
{
ptr
->
ref_coordinated
();
}
friend
void
intrusive_ptr_release
(
const
coordinated
*
ptr
)
noexcept
{
ptr
->
deref_coordinated
();
}
};
}
// namespace caf::flow
libcaf_core/caf/flow/coordinator.hpp
View file @
04e02412
...
@@ -9,7 +9,6 @@
...
@@ -9,7 +9,6 @@
#include "caf/flow/fwd.hpp"
#include "caf/flow/fwd.hpp"
#include "caf/flow/subscription.hpp"
#include "caf/flow/subscription.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/ref_counted.hpp"
#include "caf/timespan.hpp"
#include "caf/timespan.hpp"
#include <chrono>
#include <chrono>
...
@@ -22,10 +21,6 @@ namespace caf::flow {
...
@@ -22,10 +21,6 @@ namespace caf::flow {
/// objects since the coordinator guarantees synchronous execution.
/// objects since the coordinator guarantees synchronous execution.
class
CAF_CORE_EXPORT
coordinator
{
class
CAF_CORE_EXPORT
coordinator
{
public:
public:
// -- friends ----------------------------------------------------------------
friend
class
subscription_impl
;
// -- member types -----------------------------------------------------------
// -- member types -----------------------------------------------------------
/// A time point of the monotonic clock.
/// A time point of the monotonic clock.
...
...
libcaf_core/caf/flow/fwd.hpp
View file @
04e02412
...
@@ -121,9 +121,6 @@ struct output_type_oracle {
...
@@ -121,9 +121,6 @@ struct output_type_oracle {
template
<
class
T
>
template
<
class
T
>
using
output_type_t
=
typename
output_type_oracle
<
T
>::
type
;
using
output_type_t
=
typename
output_type_oracle
<
T
>::
type
;
template
<
class
T
>
class
merger_impl
;
template
<
class
>
template
<
class
>
struct
has_impl_include
{
struct
has_impl_include
{
static
constexpr
bool
value
=
false
;
static
constexpr
bool
value
=
false
;
...
...
libcaf_core/caf/flow/item_publisher.hpp
0 → 100644
View file @
04e02412
// 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/flow/coordinator.hpp"
#include "caf/flow/observable.hpp"
#include "caf/flow/op/mcast.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/make_counted.hpp"
#include <cstdint>
namespace
caf
::
flow
{
template
<
class
T
>
class
item_publisher
{
public:
explicit
item_publisher
(
coordinator
*
ctx
)
{
pimpl_
=
make_counted
<
op
::
mcast
<
T
>>
(
ctx
);
}
item_publisher
(
item_publisher
&&
)
=
default
;
item_publisher
&
operator
=
(
item_publisher
&&
)
=
default
;
~
item_publisher
()
{
pimpl_
->
close
();
}
/// Pushes an item to all subscribed observers. The publisher drops the item
/// if no subscriber exists.
void
push
(
const
T
&
item
)
{
pimpl_
->
push_all
(
item
);
}
/// Pushes the items in range `[first, last)` to all subscribed observers. The
/// publisher drops the items if no subscriber exists.
template
<
class
Iterator
,
class
Sentinel
>
void
push
(
Iterator
first
,
Sentinel
last
)
{
while
(
first
!=
last
)
push
(
*
first
++
);
}
/// Pushes the items from the initializer list to all subscribed observers.
/// The publisher drops the items if no subscriber exists.
void
push
(
std
::
initializer_list
<
T
>
items
)
{
for
(
auto
&
item
:
items
)
push
(
item
);
}
/// Closes the publisher, eventually emitting on_complete on all observers.
void
close
()
{
pimpl_
->
close
();
}
/// Closes the publisher, eventually emitting on_error on all observers.
void
abort
(
const
error
&
reason
)
{
pimpl_
->
abort
(
reason
);
}
/// Queries how many items the publisher may emit immediately to subscribed
/// observers.
size_t
demand
()
const
noexcept
{
return
pimpl_
->
min_demand
();
}
/// Queries how many items are currently waiting in a buffer until the
/// observer requests additional items.
size_t
buffered
()
const
noexcept
{
return
pimpl_
->
max_buffered
();
}
/// Queries whether there is at least one observer subscribed to the operator.
bool
has_observers
()
const
noexcept
{
return
pimpl_
->
has_observers
();
}
/// Converts the publisher to an @ref observable.
observable
<
T
>
as_observable
()
const
{
return
observable
<
T
>
{
pimpl_
};
}
/// Subscribes a new @ref observer to the output of the publisher.
disposable
subscribe
(
observer
<
T
>
out
)
{
return
pimpl_
->
subscribe
(
out
);
}
private:
intrusive_ptr
<
op
::
mcast
<
T
>>
pimpl_
;
};
}
// namespace caf::flow
libcaf_core/caf/flow/merge.hpp
View file @
04e02412
...
@@ -11,17 +11,5 @@ namespace caf::flow {
...
@@ -11,17 +11,5 @@ namespace caf::flow {
/// Combines the items emitted from `pub` and `pubs...` to appear as a single
/// Combines the items emitted from `pub` and `pubs...` to appear as a single
/// stream of items.
/// stream of items.
template
<
class
Observable
,
class
...
Observables
>
observable
<
typename
Observable
::
output_type
>
merge
(
Observable
x
,
Observables
...
xs
)
{
using
output_type
=
output_type_t
<
Observable
>
;
static_assert
(
(
std
::
is_same_v
<
output_type
,
output_type_t
<
Observables
>>
&&
...));
auto
hdl
=
std
::
move
(
x
).
as_observable
();
auto
ptr
=
make_counted
<
merger_impl
<
output_type
>>
(
hdl
.
ptr
()
->
ctx
());
ptr
->
add
(
std
::
move
(
hdl
));
(
ptr
->
add
(
std
::
move
(
xs
).
as_observable
()),
...);
return
observable
<
output_type
>
{
std
::
move
(
ptr
)};
}
}
// namespace caf::flow
}
// namespace caf::flow
libcaf_core/caf/flow/observable.hpp
View file @
04e02412
...
@@ -4,11 +4,6 @@
...
@@ -4,11 +4,6 @@
#pragma once
#pragma once
#include <cstddef>
#include <numeric>
#include <type_traits>
#include <vector>
#include "caf/async/consumer.hpp"
#include "caf/async/consumer.hpp"
#include "caf/async/producer.hpp"
#include "caf/async/producer.hpp"
#include "caf/async/spsc_buffer.hpp"
#include "caf/async/spsc_buffer.hpp"
...
@@ -17,10 +12,18 @@
...
@@ -17,10 +12,18 @@
#include "caf/detail/core_export.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/detail/unordered_flat_map.hpp"
#include "caf/detail/unordered_flat_map.hpp"
#include "caf/disposable.hpp"
#include "caf/disposable.hpp"
#include "caf/flow/coordinated.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/fwd.hpp"
#include "caf/flow/fwd.hpp"
#include "caf/flow/observable_decl.hpp"
#include "caf/flow/observable_state.hpp"
#include "caf/flow/observable_state.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/op/base.hpp"
#include "caf/flow/op/concat.hpp"
#include "caf/flow/op/from_resource.hpp"
#include "caf/flow/op/from_steps.hpp"
#include "caf/flow/op/merge.hpp"
#include "caf/flow/op/prefix_and_tail.hpp"
#include "caf/flow/step.hpp"
#include "caf/flow/step.hpp"
#include "caf/flow/subscription.hpp"
#include "caf/flow/subscription.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/intrusive_ptr.hpp"
...
@@ -29,330 +32,13 @@
...
@@ -29,330 +32,13 @@
#include "caf/ref_counted.hpp"
#include "caf/ref_counted.hpp"
#include "caf/sec.hpp"
#include "caf/sec.hpp"
namespace
caf
::
flow
{
#include <cstddef>
#include <functional>
/// Represents a potentially unbound sequence of values.
#include <numeric>
template
<
class
T
>
#include <type_traits>
class
observable
{
#include <vector>
public:
using
output_type
=
T
;
/// Internal interface of an `observable`.
class
impl
:
public
disposable_impl
{
public:
// -- member types ---------------------------------------------------------
using
output_type
=
T
;
// -- properties -----------------------------------------------------------
virtual
coordinator
*
ctx
()
const
noexcept
=
0
;
// -- conversions ----------------------------------------------------------
observable
as_observable
()
noexcept
;
// -- flow processing ------------------------------------------------------
/// Subscribes a new observer.
virtual
disposable
subscribe
(
observer
<
T
>
what
)
=
0
;
// -- convenience functions ------------------------------------------------
/// Calls `on_error` on the `sink` with given `code` and returns a
/// default-constructed @ref disposable.
disposable
reject_subscription
(
observer_impl
<
T
>*
sink
,
sec
code
)
{
sink
->
on_error
(
make_error
(
code
));
return
disposable
{};
}
/// @copydoc reject_subscription
disposable
reject_subscription
(
observer
<
T
>&
sink
,
sec
code
)
{
return
reject_subscription
(
sink
.
ptr
(),
code
);
}
};
explicit
observable
(
intrusive_ptr
<
impl
>
pimpl
)
noexcept
:
pimpl_
(
std
::
move
(
pimpl
))
{
// nop
}
observable
&
operator
=
(
std
::
nullptr_t
)
noexcept
{
pimpl_
.
reset
();
return
*
this
;
}
observable
()
noexcept
=
default
;
observable
(
observable
&&
)
noexcept
=
default
;
observable
(
const
observable
&
)
noexcept
=
default
;
observable
&
operator
=
(
observable
&&
)
noexcept
=
default
;
observable
&
operator
=
(
const
observable
&
)
noexcept
=
default
;
void
dispose
()
{
if
(
pimpl_
)
{
pimpl_
->
dispose
();
pimpl_
=
nullptr
;
}
}
disposable
as_disposable
()
const
&
noexcept
{
return
disposable
{
pimpl_
};
}
disposable
as_disposable
()
&&
noexcept
{
return
disposable
{
std
::
move
(
pimpl_
)};
}
/// @copydoc impl::subscribe
disposable
subscribe
(
observer
<
T
>
what
)
{
if
(
pimpl_
)
{
return
pimpl_
->
subscribe
(
std
::
move
(
what
));
}
else
{
what
.
on_error
(
make_error
(
sec
::
invalid_observable
));
return
disposable
{};
}
}
/// Creates a new observer that pushes all observed items to the resource.
disposable
subscribe
(
async
::
producer_resource
<
T
>
resource
);
/// Returns a transformation that applies a step function to each input.
template
<
class
Step
>
transformation
<
Step
>
transform
(
Step
step
);
/// Registers a callback for `on_complete` events.
template
<
class
F
>
auto
do_on_complete
(
F
f
)
{
return
transform
(
do_on_complete_step
<
T
,
F
>
{
std
::
move
(
f
)});
}
/// Registers a callback for `on_error` events.
template
<
class
F
>
auto
do_on_error
(
F
f
)
{
return
transform
(
do_on_error_step
<
T
,
F
>
{
std
::
move
(
f
)});
}
/// Registers a callback that runs on `on_complete` or `on_error`.
template
<
class
F
>
auto
do_finally
(
F
f
)
{
return
transform
(
do_finally_step
<
T
,
F
>
{
std
::
move
(
f
)});
}
/// Catches errors by converting them into errors instead.
auto
on_error_complete
()
{
return
transform
(
on_error_complete_step
<
T
>
{});
}
/// Returns a transformation that selects only the first `n` items.
transformation
<
limit_step
<
T
>>
take
(
size_t
n
);
/// Returns a transformation that selects only items that satisfy `predicate`.
template
<
class
Predicate
>
transformation
<
filter_step
<
Predicate
>>
filter
(
Predicate
prediate
);
/// Returns a transformation that applies `f` to each input and emits the
/// result of the function application.
template
<
class
F
>
transformation
<
map_step
<
F
>>
map
(
F
f
);
/// Calls `on_next` for each item emitted by this observable.
template
<
class
OnNext
>
disposable
for_each
(
OnNext
on_next
);
/// Calls `on_next` for each item and `on_error` for each error emitted by
/// this observable.
template
<
class
OnNext
,
class
OnError
>
disposable
for_each
(
OnNext
on_next
,
OnError
on_error
);
/// Calls `on_next` for each item, `on_error` for each error and `on_complete`
/// for each completion signal emitted by this observable.
template
<
class
OnNext
,
class
OnError
,
class
OnComplete
>
disposable
for_each
(
OnNext
on_next
,
OnError
on_error
,
OnComplete
on_complete
);
/// Returns a transformation that emits items by merging the outputs of all
/// observables returned by `f`.
template
<
class
F
>
auto
flat_map
(
F
f
);
/// Returns a transformation that emits items from optional values returned by
/// `f`.
template
<
class
F
>
transformation
<
flat_map_optional_step
<
F
>>
flat_map_optional
(
F
f
);
/// Returns a transformation that emits items by concatenating the outputs of
/// all observables returned by `f`.
template
<
class
F
>
auto
concat_map
(
F
f
);
/// Takes @p prefix_size elements from this observable and emits it in a tuple
/// containing an observable for the remaining elements as the second value.
/// The returned observable either emits a single element (the tuple) or none
/// if this observable never produces sufficient elements for the prefix.
/// @pre `prefix_size > 0`
observable
<
cow_tuple
<
std
::
vector
<
T
>
,
observable
<
T
>>>
prefix_and_tail
(
size_t
prefix_size
);
/// Similar to `prefix_and_tail(1)` but passes the single element directly in
/// the tuple instead of wrapping it in a list.
observable
<
cow_tuple
<
T
,
observable
<
T
>>>
head_and_tail
();
/// Creates an asynchronous resource that makes emitted items available in a
/// spsc buffer.
async
::
consumer_resource
<
T
>
to_resource
(
size_t
buffer_size
,
size_t
min_request_size
);
async
::
consumer_resource
<
T
>
to_resource
()
{
return
to_resource
(
defaults
::
flow
::
buffer_size
,
defaults
::
flow
::
min_demand
);
}
observable
observe_on
(
coordinator
*
other
,
size_t
buffer_size
,
size_t
min_request_size
);
observable
observe_on
(
coordinator
*
other
)
{
return
observe_on
(
other
,
defaults
::
flow
::
buffer_size
,
defaults
::
flow
::
min_demand
);
}
const
observable
&
as_observable
()
const
&
noexcept
{
return
std
::
move
(
*
this
);
}
observable
&&
as_observable
()
&&
noexcept
{
return
std
::
move
(
*
this
);
}
bool
valid
()
const
noexcept
{
return
pimpl_
!=
nullptr
;
}
explicit
operator
bool
()
const
noexcept
{
return
valid
();
}
bool
operator
!
()
const
noexcept
{
return
!
valid
();
}
impl
*
ptr
()
{
return
pimpl_
.
get
();
}
const
impl
*
ptr
()
const
{
return
pimpl_
.
get
();
}
const
intrusive_ptr
<
impl
>&
as_intrusive_ptr
()
const
&
noexcept
{
return
pimpl_
;
}
intrusive_ptr
<
impl
>&&
as_intrusive_ptr
()
&&
noexcept
{
return
std
::
move
(
pimpl_
);
}
void
swap
(
observable
&
other
)
{
pimpl_
.
swap
(
other
.
pimpl_
);
}
/// @pre `valid()`
coordinator
*
ctx
()
const
{
return
pimpl_
->
ctx
();
}
private:
intrusive_ptr
<
impl
>
pimpl_
;
};
template
<
class
T
>
observable
<
T
>
observable
<
T
>::
impl
::
as_observable
()
noexcept
{
return
observable
<
T
>
{
intrusive_ptr
{
this
}};
}
/// @relates observable
template
<
class
T
>
using
observable_impl
=
typename
observable
<
T
>::
impl
;
/// Default base type for observable implementation types.
template
<
class
T
>
class
observable_impl_base
:
public
ref_counted
,
public
observable_impl
<
T
>
,
public
subscription
::
listener
{
public:
// -- member types -----------------------------------------------------------
using
super
=
observable_impl
<
T
>
;
using
output_type
=
T
;
// -- constructors, destructors, and assignment operators --------------------
explicit
observable_impl_base
(
coordinator
*
ctx
)
:
ctx_
(
ctx
)
{
// nop
}
// -- implementation of disposable_impl --------------------------------------
disposable
as_disposable
()
noexcept
{
// We need to implement this only for disambiguation since `as_disposable`
// exists in multiple base classes.
return
subscription
::
listener
::
as_disposable
();
}
void
ref_disposable
()
const
noexcept
final
{
this
->
ref
();
}
void
deref_disposable
()
const
noexcept
final
{
this
->
deref
();
}
// -- implementation of observable_impl<T> -----------------------------------
coordinator
*
ctx
()
const
noexcept
final
{
return
ctx_
;
}
// -- convenience functions --------------------------------------------------
/// Creates a subscription for `sink` and returns a @ref disposable to cancel
/// the observer.
disposable
do_subscribe
(
observer_impl
<
T
>*
sink
)
{
sink
->
on_subscribe
(
subscription
::
default_impl
::
make
(
ctx_
,
this
,
sink
));
// Note: we do NOT return the subscription here because this object is
// private to the observer. Outside code cancels flows by calling
// dispose on the observer.
return
sink
->
as_disposable
();
}
/// @copydoc do_subscribe
disposable
do_subscribe
(
observer
<
T
>&
sink
)
{
return
do_subscribe
(
sink
.
ptr
());
}
protected:
// -- member variables -------------------------------------------------------
coordinator
*
ctx_
;
};
/// Convenience function for creating a sub-type of @ref observable_impl without
/// exposing the derived type.
template
<
class
Impl
,
class
...
Ts
>
intrusive_ptr
<
observable_impl
<
typename
Impl
::
output_type
>>
make_observable_impl
(
coordinator
*
ctx
,
Ts
&&
...
xs
)
{
using
out_t
=
typename
Impl
::
output_type
;
using
res_t
=
intrusive_ptr
<
observable_impl
<
out_t
>>
;
observable_impl
<
out_t
>*
ptr
=
new
Impl
(
ctx
,
std
::
forward
<
Ts
>
(
xs
)...);
return
res_t
{
ptr
,
false
};
}
/// Convenience function for creating an @ref observable from a concrete
namespace
caf
::
flow
{
/// implementation type.
template
<
class
Impl
,
class
...
Ts
>
observable
<
typename
Impl
::
output_type
>
make_observable
(
coordinator
*
ctx
,
Ts
&&
...
xs
)
{
using
res_t
=
observable
<
typename
Impl
::
output_type
>
;
return
res_t
{
make_observable_impl
<
Impl
>
(
ctx
,
std
::
forward
<
Ts
>
(
xs
)...)};
}
/// Base type for classes that represent a definition of an `observable` which
/// Base type for classes that represent a definition of an `observable` which
/// has not yet been converted to an actual `observable`.
/// has not yet been converted to an actual `observable`.
...
@@ -363,1454 +49,401 @@ public:
...
@@ -363,1454 +49,401 @@ public:
template
<
class
OnNext
>
template
<
class
OnNext
>
auto
for_each
(
OnNext
on_next
)
&&
{
auto
for_each
(
OnNext
on_next
)
&&
{
return
lift
().
for_each
(
std
::
move
(
on_next
));
return
alloc
().
for_each
(
std
::
move
(
on_next
));
}
}
template
<
class
OnNext
,
class
OnError
>
template
<
class
OnNext
,
class
OnError
>
auto
for_each
(
OnNext
on_next
,
OnError
on_error
)
&&
{
auto
for_each
(
OnNext
on_next
,
OnError
on_error
)
&&
{
return
lift
().
for_each
(
std
::
move
(
on_next
),
std
::
move
(
on_error
));
return
alloc
().
for_each
(
std
::
move
(
on_next
),
std
::
move
(
on_error
));
}
}
template
<
class
OnNext
,
class
OnError
,
class
OnComplete
>
template
<
class
OnNext
,
class
OnError
,
class
OnComplete
>
auto
for_each
(
OnNext
on_next
,
OnError
on_error
,
OnComplete
on_complete
)
&&
{
auto
for_each
(
OnNext
on_next
,
OnError
on_error
,
OnComplete
on_complete
)
&&
{
return
lift
().
for_each
(
std
::
move
(
on_next
),
std
::
move
(
on_error
),
return
alloc
().
for_each
(
std
::
move
(
on_next
),
std
::
move
(
on_error
),
std
::
move
(
on_complete
));
std
::
move
(
on_complete
));
}
template
<
class
...
Inputs
>
auto
merge
(
Inputs
&&
...
xs
)
&&
{
return
alloc
().
as_observable
().
merge
(
std
::
forward
<
Inputs
>
(
xs
)...);
}
template
<
class
...
Inputs
>
auto
concat
(
Inputs
&&
...
xs
)
&&
{
return
alloc
().
as_observable
().
concat
(
std
::
forward
<
Inputs
>
(
xs
)...);
}
}
template
<
class
F
>
template
<
class
F
>
auto
flat_map
(
F
f
)
&&
{
auto
flat_map
(
F
f
)
&&
{
return
lift
().
flat_map
(
std
::
move
(
f
));
return
alloc
().
flat_map
(
std
::
move
(
f
));
}
}
template
<
class
F
>
template
<
class
F
>
auto
concat_map
(
F
f
)
&&
{
auto
concat_map
(
F
f
)
&&
{
return
lift
().
concat_map
(
std
::
move
(
f
));
return
alloc
().
concat_map
(
std
::
move
(
f
));
}
}
observable
<
cow_tuple
<
std
::
vector
<
T
>
,
observable
<
T
>>>
observable
<
cow_tuple
<
std
::
vector
<
T
>
,
observable
<
T
>>>
prefix_and_tail
(
size_t
prefix_size
)
&&
{
prefix_and_tail
(
size_t
prefix_size
)
&&
{
return
lift
().
prefix_and_tail
(
prefix_size
);
return
alloc
().
prefix_and_tail
(
prefix_size
);
}
}
observable
<
cow_tuple
<
T
,
observable
<
T
>>>
head_and_tail
()
&&
{
observable
<
cow_tuple
<
T
,
observable
<
T
>>>
head_and_tail
()
&&
{
return
lift
().
head_and_tail
();
return
alloc
().
head_and_tail
();
}
}
disposable
subscribe
(
observer
<
T
>
what
)
&&
{
disposable
subscribe
(
observer
<
T
>
what
)
&&
{
return
lift
().
subscribe
(
std
::
move
(
what
));
return
alloc
().
subscribe
(
std
::
move
(
what
));
}
}
disposable
subscribe
(
async
::
producer_resource
<
T
>
resource
)
&&
{
disposable
subscribe
(
async
::
producer_resource
<
T
>
resource
)
&&
{
return
lift
().
subscribe
(
std
::
move
(
resource
));
return
alloc
().
subscribe
(
std
::
move
(
resource
));
}
}
async
::
consumer_resource
<
T
>
to_resource
()
&&
{
async
::
consumer_resource
<
T
>
to_resource
()
&&
{
return
lift
().
to_resource
();
return
alloc
().
to_resource
();
}
}
async
::
consumer_resource
<
T
>
to_resource
(
size_t
buffer_size
,
async
::
consumer_resource
<
T
>
to_resource
(
size_t
buffer_size
,
size_t
min_request_size
)
&&
{
size_t
min_request_size
)
&&
{
return
lift
().
to_resource
(
buffer_size
,
min_request_size
);
return
alloc
().
to_resource
(
buffer_size
,
min_request_size
);
}
}
observable
<
T
>
observe_on
(
coordinator
*
other
)
&&
{
observable
<
T
>
observe_on
(
coordinator
*
other
)
&&
{
return
lift
().
observe_on
(
other
);
return
alloc
().
observe_on
(
other
);
}
}
observable
<
T
>
observe_on
(
coordinator
*
other
,
size_t
buffer_size
,
observable
<
T
>
observe_on
(
coordinator
*
other
,
size_t
buffer_size
,
size_t
min_request_size
)
&&
{
size_t
min_request_size
)
&&
{
return
lift
().
observe_on
(
other
,
buffer_size
,
min_request_size
);
return
alloc
().
observe_on
(
other
,
buffer_size
,
min_request_size
);
}
}
virtual
observable
<
T
>
as_observable
()
&&
=
0
;
virtual
observable
<
T
>
as_observable
()
&&
=
0
;
private:
private:
decltype
(
auto
)
lift
()
{
/// Allocates and returns an actual @ref observable.
decltype
(
auto
)
alloc
()
{
return
std
::
move
(
*
this
).
as_observable
();
return
std
::
move
(
*
this
).
as_observable
();
}
}
};
};
template
<
class
In
,
class
Out
>
// -- transformation -----------------------------------------------------------
class
processor
{
public:
class
impl
:
public
observer_impl
<
In
>
,
public
observable_impl
<
Out
>
{
public:
observer_impl
<
In
>*
as_observer_ptr
()
noexcept
{
return
this
;
}
observable_impl
<
In
>*
as_observable_ptr
()
noexcept
{
return
this
;
}
};
explicit
processor
(
intrusive_ptr
<
impl
>
pimpl
)
noexcept
:
pimpl_
(
std
::
move
(
pimpl
))
{
// nop
}
processor
&
operator
=
(
std
::
nullptr_t
)
noexcept
{
/// A special type of observer that applies a series of transformation steps to
pimpl_
.
reset
();
/// its input before broadcasting the result as output.
return
*
this
;
template
<
class
Step
,
class
...
Steps
>
}
class
transformation
final
:
public
observable_def
<
steps_output_type_t
<
Step
,
Steps
...
>>
{
public:
using
input_type
=
typename
Step
::
input_type
;
using
input_type
=
In
;
using
output_type
=
steps_output_type_t
<
Step
,
Steps
...
>
;
using
output_type
=
Out
;
template
<
class
Tuple
>
transformation
(
observable
<
input_type
>
source
,
Tuple
&&
steps
)
:
source_
(
std
::
move
(
source
)),
steps_
(
std
::
move
(
steps
))
{
// nop
}
processor
()
noexcept
=
default
;
transformation
()
=
delete
;
processor
(
processor
&&
)
noexcept
=
default
;
transformation
(
const
transformation
&
)
=
delete
;
processor
(
const
processor
&
)
noexcept
=
default
;
transformation
&
operator
=
(
const
transformation
&
)
=
delete
;
processor
&
operator
=
(
processor
&&
)
noexcept
=
default
;
processor
&
operator
=
(
const
processor
&
)
noexcept
=
default
;
// -- conversion -------------------------------------------------------------
transformation
(
transformation
&&
)
=
default
;
transformation
&
operator
=
(
transformation
&&
)
=
default
;
disposable
as_disposable
()
const
&
noexcept
{
/// @copydoc observable::transform
return
as_observable
().
as_disposable
();
template
<
class
NewStep
>
transformation
<
Step
,
Steps
...,
NewStep
>
transform
(
NewStep
step
)
&&
{
return
{
std
::
move
(
source_
),
std
::
tuple_cat
(
std
::
move
(
steps_
),
std
::
make_tuple
(
std
::
move
(
step
)))};
}
}
disposable
as_disposable
()
&&
noexcept
{
auto
take
(
size_t
n
)
&&
{
return
std
::
move
(
*
this
).
as_observable
().
as_disposable
(
);
return
std
::
move
(
*
this
).
transform
(
limit_step
<
output_type
>
{
n
}
);
}
}
observer
<
In
>
as_observer
()
const
&
noexcept
{
template
<
class
Predicate
>
return
pimpl_
->
as_observer
();
auto
filter
(
Predicate
predicate
)
&&
{
return
std
::
move
(
*
this
).
transform
(
filter_step
<
Predicate
>
{
std
::
move
(
predicate
)});
}
}
observer
<
In
>
as_observer
()
&&
noexcept
{
template
<
class
Predicate
>
auto
raw
=
pimpl_
.
release
()
->
as_observer_ptr
();
auto
take_while
(
Predicate
predicate
)
&&
{
auto
ptr
=
intrusive_ptr
<
observer_impl
<
In
>>
{
raw
,
false
};
return
std
::
move
(
*
this
).
transform
(
return
observer
<
In
>
{
std
::
move
(
ptr
)}
;
take_while_step
<
Predicate
>
{
std
::
move
(
predicate
)})
;
}
}
observable
<
Out
>
as_observable
()
const
&
noexcept
{
template
<
class
Reducer
>
return
pimpl_
->
as_observable
();
auto
reduce
(
output_type
init
,
Reducer
reducer
)
&&
{
return
std
::
move
(
*
this
).
transform
(
reduce_step
<
output_type
,
Reducer
>
{
init
,
reducer
});
}
}
observable
<
Out
>
as_observable
()
&&
noexcept
{
auto
sum
()
&&
{
auto
raw
=
pimpl_
.
release
()
->
as_observable_ptr
();
return
std
::
move
(
*
this
).
reduce
(
output_type
{},
std
::
plus
<
output_type
>
{});
auto
ptr
=
intrusive_ptr
<
observable_impl
<
Out
>>
{
raw
,
false
};
return
observable
<
Out
>
{
std
::
move
(
ptr
)};
}
}
private:
auto
distinct
()
&&
{
intrusive_ptr
<
impl
>
pimpl_
;
return
std
::
move
(
*
this
).
transform
(
distinct_step
<
output_type
>
{});
};
/// @relates processor
template
<
class
In
,
class
Out
=
In
>
using
processor_impl
=
typename
processor
<
In
,
Out
>::
impl
;
/// Default base type for processor implementation types.
/// @relates processor
template
<
class
In
,
class
Out
>
class
processor_impl_base
:
public
ref_counted
,
public
processor_impl
<
In
,
Out
>
,
public
subscription
::
listener
{
public:
// -- member types -----------------------------------------------------------
using
super
=
processor_impl
<
In
,
Out
>
;
using
input_type
=
In
;
using
output_type
=
Out
;
// -- constructors, destructors, and assignment operators --------------------
explicit
processor_impl_base
(
coordinator
*
ctx
)
:
ctx_
(
ctx
)
{
// nop
}
}
// -- implementation of disposable_impl --------------------------------------
template
<
class
F
>
auto
map
(
F
f
)
&&
{
disposable
as_disposable
()
noexcept
{
return
std
::
move
(
*
this
).
transform
(
map_step
<
F
>
{
std
::
move
(
f
)});
// We need to implement this only for disambiguation since `as_disposable`
// exists in multiple base classes.
return
subscription
::
listener
::
as_disposable
();
}
}
void
ref_disposable
()
const
noexcept
final
{
template
<
class
F
>
this
->
ref
();
auto
flat_map_optional
(
F
f
)
&&
{
return
std
::
move
(
*
this
).
transform
(
flat_map_optional_step
<
F
>
{
std
::
move
(
f
)});
}
}
void
deref_disposable
()
const
noexcept
final
{
template
<
class
F
>
this
->
deref
();
auto
do_on_next
(
F
f
)
&&
{
return
std
::
move
(
*
this
)
//
.
transform
(
do_on_next_step
<
output_type
,
F
>
{
std
::
move
(
f
)});
}
}
// -- implementation of processor_impl<T> -----------------------------------
template
<
class
F
>
auto
do_on_complete
(
F
f
)
&&
{
coordinator
*
ctx
()
const
noexcept
final
{
return
std
::
move
(
*
this
)
//
return
ctx_
;
.
transform
(
do_on_complete_step
<
output_type
,
F
>
{
std
::
move
(
f
)})
;
}
}
// -- convenience functions --------------------------------------------------
template
<
class
F
>
auto
do_on_error
(
F
f
)
&&
{
return
std
::
move
(
*
this
)
//
.
transform
(
do_on_error_step
<
output_type
,
F
>
{
std
::
move
(
f
)});
}
/// Creates a subscription for `sink` and returns a @ref disposable to cancel
template
<
class
F
>
/// the observer.
auto
do_finally
(
F
f
)
&&
{
disposable
do_subscribe
(
observer_impl
<
Out
>*
sink
)
{
return
std
::
move
(
*
this
)
//
sink
->
on_subscribe
(
subscription
::
default_impl
::
make
(
ctx_
,
this
,
sink
));
.
transform
(
do_finally_step
<
output_type
,
F
>
{
std
::
move
(
f
)});
// Note: we do NOT return the subscription here because this object is
// private to the observer. Outside code cancels flows by calling
// dispose on the observer.
return
disposable
{
intrusive_ptr
<
disposable_impl
>
{
sink
}};
}
}
/// @copydoc do_subscribe
auto
on_error_complete
()
{
disposable
do_subscribe
(
observer
<
Out
>&
sink
)
{
return
std
::
move
(
*
this
)
//
return
do_subscribe
(
sink
.
ptr
()
);
.
transform
(
on_error_complete_step
<
output_type
>
{}
);
}
}
protected:
observable
<
output_type
>
as_observable
()
&&
{
// -- member variables -------------------------------------------------------
using
impl_t
=
op
::
from_steps
<
input_type
,
Step
,
Steps
...
>
;
return
make_observable
<
impl_t
>
(
source_
.
ctx
(),
source_
.
pimpl
(),
std
::
move
(
steps_
));
}
coordinator
*
ctx_
;
private:
observable
<
input_type
>
source_
;
std
::
tuple
<
Step
,
Steps
...
>
steps_
;
};
};
// --
representing an error as an observable
-----------------------------------
// --
observable::transform -----------------
-----------------------------------
template
<
class
T
>
template
<
class
T
>
class
observable_error_impl
:
public
ref_counted
,
public
observable_impl
<
T
>
{
template
<
class
Step
>
public:
transformation
<
Step
>
observable
<
T
>::
transform
(
Step
step
)
{
// -- constructors, destructors, and assignment operators --------------------
static_assert
(
std
::
is_same_v
<
typename
Step
::
input_type
,
T
>
,
"step object does not match the input type"
);
observable_error_impl
(
coordinator
*
ctx
,
error
what
)
return
{
*
this
,
std
::
forward_as_tuple
(
std
::
move
(
step
))};
:
ctx_
(
ctx
),
what_
(
std
::
move
(
what
))
{
}
// nop
}
// -- implementation of disposable_impl --------------------------------------
void
ref_disposable
()
const
noexcept
final
{
// -- observable::take ---------------------------------------------------------
this
->
ref
();
}
void
deref_disposable
()
const
noexcept
final
{
template
<
class
T
>
this
->
deref
();
transformation
<
limit_step
<
T
>>
observable
<
T
>::
take
(
size_t
n
)
{
}
return
{
*
this
,
std
::
forward_as_tuple
(
limit_step
<
T
>
{
n
})};
}
void
dispose
()
override
{
// -- observable::filter -------------------------------------------------------
// nop
}
bool
disposed
()
const
noexcept
override
{
template
<
class
T
>
return
true
;
template
<
class
Predicate
>
}
transformation
<
filter_step
<
Predicate
>>
observable
<
T
>::
filter
(
Predicate
predicate
)
{
using
step_type
=
filter_step
<
Predicate
>
;
static_assert
(
std
::
is_same_v
<
typename
step_type
::
input_type
,
T
>
,
"predicate does not match the input type"
);
return
{
*
this
,
std
::
forward_as_tuple
(
step_type
{
std
::
move
(
predicate
)})};
}
// -- implementation of observable<T>::impl ----------------------------------
// -- observable::take_while ---------------------------------------------------
coordinator
*
ctx
()
const
noexcept
final
{
return
ctx_
;
}
disposable
subscribe
(
observer
<
T
>
what
)
override
{
what
.
on_error
(
what_
);
return
{};
}
private:
coordinator
*
ctx_
;
error
what_
;
};
// -- broadcasting -------------------------------------------------------------
/// Broadcasts its input to all observers without modifying it.
template
<
class
Step
,
class
...
Steps
>
class
broadcaster_impl
:
public
processor_impl_base
<
typename
Step
::
input_type
,
steps_output_type_t
<
Step
,
Steps
...
>>
{
public:
// -- member types -----------------------------------------------------------
using
input_type
=
typename
Step
::
input_type
;
using
output_type
=
steps_output_type_t
<
Step
,
Steps
...
>
;
using
super
=
processor_impl_base
<
input_type
,
output_type
>
;
// -- friends ----------------------------------------------------------------
CAF_INTRUSIVE_PTR_FRIENDS
(
broadcaster_impl
)
// -- constructors, destructors, and assignment operators --------------------
template
<
class
...
Ts
>
explicit
broadcaster_impl
(
coordinator
*
ctx
,
Ts
&&
...
step_args
)
:
super
(
ctx
),
steps_
(
std
::
forward
<
Ts
>
(
step_args
)...)
{
// nop
}
// -- implementation of disposable_impl --------------------------------------
void
dispose
()
override
{
CAF_LOG_TRACE
(
""
);
term_
.
dispose
();
}
bool
disposed
()
const
noexcept
override
{
return
!
term_
.
active
();
}
// -- implementation of observer<T>::impl ------------------------------------
void
on_subscribe
(
subscription
sub
)
override
{
if
(
term_
.
start
(
sub
))
sub_
=
std
::
move
(
sub
);
}
void
on_next
(
const
input_type
&
item
)
override
{
if
(
!
term_
.
finalized
())
{
auto
f
=
[
this
,
&
item
](
auto
&
step
,
auto
&
...
steps
)
{
return
step
.
on_next
(
item
,
steps
...,
term_
);
};
auto
still_running
=
std
::
apply
(
f
,
steps_
);
term_
.
push
();
if
(
!
still_running
&&
sub_
)
{
CAF_ASSERT
(
!
term_
.
active
());
sub_
.
cancel
();
sub_
=
nullptr
;
}
}
}
void
on_complete
()
override
{
if
(
term_
.
active
())
{
auto
f
=
[
this
](
auto
&
step
,
auto
&
...
steps
)
{
step
.
on_complete
(
steps
...,
term_
);
};
std
::
apply
(
f
,
steps_
);
sub_
=
nullptr
;
}
}
void
on_error
(
const
error
&
what
)
override
{
if
(
term_
.
active
())
{
auto
f
=
[
this
,
&
what
](
auto
&
step
,
auto
&
...
steps
)
{
step
.
on_error
(
what
,
steps
...,
term_
);
};
std
::
apply
(
f
,
steps_
);
sub_
=
nullptr
;
}
}
// -- implementation of subscription::listener -------------------------------
void
on_request
(
disposable_impl
*
sink
,
size_t
n
)
override
{
CAF_LOG_TRACE
(
CAF_ARG
(
n
));
term_
.
on_request
(
sub_
,
sink
,
n
);
}
void
on_cancel
(
disposable_impl
*
sink
)
override
{
CAF_LOG_TRACE
(
""
);
term_
.
on_cancel
(
sub_
,
sink
);
}
// -- implementation of observable<T>::impl ----------------------------------
disposable
subscribe
(
observer
<
output_type
>
sink
)
override
{
return
term_
.
add
(
this
,
sink
);
}
// -- properties -------------------------------------------------------------
size_t
buffered
()
const
noexcept
{
return
term_
.
buffered
();
}
observable_state
state
()
const
noexcept
{
return
term_
.
state
();
}
const
error
&
err
()
const
noexcept
{
return
term_
.
err
();
}
protected:
/// Allows us to request more items.
subscription
sub_
;
/// The processing steps that we apply before pushing data downstream.
std
::
tuple
<
Step
,
Steps
...
>
steps_
;
/// Pushes data to the observers.
broadcast_step
<
output_type
>
term_
;
};
/// @relates broadcaster_impl
template
<
class
Step
,
class
...
Steps
>
using
broadcaster_impl_ptr
=
intrusive_ptr
<
broadcaster_impl
<
Step
,
Steps
...
>>
;
/// @relates broadcaster_impl
template
<
class
T
>
broadcaster_impl_ptr
<
identity_step
<
T
>>
make_broadcaster_impl
(
coordinator
*
ctx
)
{
return
make_counted
<
broadcaster_impl
<
identity_step
<
T
>>>
(
ctx
);
}
/// @relates broadcaster_impl
template
<
class
Step
,
class
...
Steps
>
broadcaster_impl_ptr
<
Step
,
Steps
...
>
make_broadcaster_impl_from_tuple
(
coordinator
*
ctx
,
std
::
tuple
<
Step
,
Steps
...
>&&
tup
)
{
auto
f
=
[
ctx
](
Step
&&
step
,
Steps
&&
...
steps
)
{
return
make_counted
<
broadcaster_impl
<
Step
,
Steps
...
>>
(
ctx
,
std
::
move
(
step
),
std
::
move
(
steps
)...);
};
return
std
::
apply
(
f
,
std
::
move
(
tup
));
}
// -- transformation -----------------------------------------------------------
/// A special type of observer that applies a series of transformation steps to
/// its input before broadcasting the result as output.
template
<
class
Step
,
class
...
Steps
>
class
transformation
final
:
public
observable_def
<
steps_output_type_t
<
Step
,
Steps
...
>>
{
public:
using
input_type
=
typename
Step
::
input_type
;
using
output_type
=
steps_output_type_t
<
Step
,
Steps
...
>
;
template
<
class
Tuple
>
transformation
(
observable
<
input_type
>
source
,
Tuple
&&
steps
)
:
source_
(
std
::
move
(
source
)),
steps_
(
std
::
move
(
steps
))
{
// nop
}
transformation
()
=
delete
;
transformation
(
const
transformation
&
)
=
delete
;
transformation
&
operator
=
(
const
transformation
&
)
=
delete
;
transformation
(
transformation
&&
)
=
default
;
transformation
&
operator
=
(
transformation
&&
)
=
default
;
/// @copydoc observable::transform
template
<
class
NewStep
>
transformation
<
Step
,
Steps
...,
NewStep
>
transform
(
NewStep
step
)
&&
{
return
{
std
::
move
(
source_
),
std
::
tuple_cat
(
std
::
move
(
steps_
),
std
::
make_tuple
(
std
::
move
(
step
)))};
}
auto
take
(
size_t
n
)
&&
{
return
std
::
move
(
*
this
).
transform
(
limit_step
<
output_type
>
{
n
});
}
template
<
class
Predicate
>
auto
filter
(
Predicate
predicate
)
&&
{
return
std
::
move
(
*
this
).
transform
(
filter_step
<
Predicate
>
{
std
::
move
(
predicate
)});
}
template
<
class
F
>
auto
map
(
F
f
)
&&
{
return
std
::
move
(
*
this
).
transform
(
map_step
<
F
>
{
std
::
move
(
f
)});
}
template
<
class
F
>
auto
flat_map_optional
(
F
f
)
&&
{
return
std
::
move
(
*
this
).
transform
(
flat_map_optional_step
<
F
>
{
std
::
move
(
f
)});
}
template
<
class
F
>
auto
do_on_complete
(
F
f
)
&&
{
return
std
::
move
(
*
this
)
//
.
transform
(
do_on_complete_step
<
output_type
,
F
>
{
std
::
move
(
f
)});
}
template
<
class
F
>
auto
do_on_error
(
F
f
)
&&
{
return
std
::
move
(
*
this
)
//
.
transform
(
do_on_error_step
<
output_type
,
F
>
{
std
::
move
(
f
)});
}
template
<
class
F
>
auto
do_finally
(
F
f
)
&&
{
return
std
::
move
(
*
this
)
//
.
transform
(
do_finally_step
<
output_type
,
F
>
{
std
::
move
(
f
)});
}
auto
on_error_complete
()
{
return
std
::
move
(
*
this
)
//
.
transform
(
on_error_complete_step
<
output_type
>
{});
}
observable
<
output_type
>
as_observable
()
&&
{
auto
pimpl
=
make_broadcaster_impl_from_tuple
(
source_
.
ptr
()
->
ctx
(),
std
::
move
(
steps_
));
auto
res
=
pimpl
->
as_observable
();
source_
.
subscribe
(
observer
<
input_type
>
{
std
::
move
(
pimpl
)});
source_
=
nullptr
;
return
res
;
}
private:
observable
<
input_type
>
source_
;
std
::
tuple
<
Step
,
Steps
...
>
steps_
;
};
// -- observable::transform ----------------------------------------------------
template
<
class
T
>
template
<
class
Step
>
transformation
<
Step
>
observable
<
T
>::
transform
(
Step
step
)
{
static_assert
(
std
::
is_same_v
<
typename
Step
::
input_type
,
T
>
,
"step object does not match the input type"
);
return
{
*
this
,
std
::
forward_as_tuple
(
std
::
move
(
step
))};
}
// -- observable::take ---------------------------------------------------------
template
<
class
T
>
transformation
<
limit_step
<
T
>>
observable
<
T
>::
take
(
size_t
n
)
{
return
{
*
this
,
std
::
forward_as_tuple
(
limit_step
<
T
>
{
n
})};
}
// -- observable::filter -------------------------------------------------------
template
<
class
T
>
template
<
class
Predicate
>
transformation
<
filter_step
<
Predicate
>>
observable
<
T
>::
filter
(
Predicate
predicate
)
{
using
step_type
=
filter_step
<
Predicate
>
;
static_assert
(
std
::
is_same_v
<
typename
step_type
::
input_type
,
T
>
,
"predicate does not match the input type"
);
return
{
*
this
,
std
::
forward_as_tuple
(
step_type
{
std
::
move
(
predicate
)})};
}
// -- observable::map ----------------------------------------------------------
template
<
class
T
>
template
<
class
F
>
transformation
<
map_step
<
F
>>
observable
<
T
>::
map
(
F
f
)
{
using
step_type
=
map_step
<
F
>
;
static_assert
(
std
::
is_same_v
<
typename
step_type
::
input_type
,
T
>
,
"map function does not match the input type"
);
return
{
*
this
,
std
::
forward_as_tuple
(
step_type
{
std
::
move
(
f
)})};
}
// -- observable::for_each -----------------------------------------------------
template
<
class
T
>
template
<
class
OnNext
>
disposable
observable
<
T
>::
for_each
(
OnNext
on_next
)
{
auto
obs
=
make_observer
(
std
::
move
(
on_next
));
subscribe
(
observer
<
T
>
{
obs
});
return
std
::
move
(
obs
).
as_disposable
();
}
template
<
class
T
>
template
<
class
OnNext
,
class
OnError
>
disposable
observable
<
T
>::
for_each
(
OnNext
on_next
,
OnError
on_error
)
{
auto
obs
=
make_observer
(
std
::
move
(
on_next
),
std
::
move
(
on_error
));
subscribe
(
obs
);
return
std
::
move
(
obs
).
as_disposable
();
}
template
<
class
T
>
template
<
class
OnNext
,
class
OnError
,
class
OnComplete
>
disposable
observable
<
T
>::
for_each
(
OnNext
on_next
,
OnError
on_error
,
OnComplete
on_complete
)
{
auto
obs
=
make_observer
(
std
::
move
(
on_next
),
std
::
move
(
on_error
),
std
::
move
(
on_complete
));
subscribe
(
obs
);
return
std
::
move
(
obs
).
as_disposable
();
}
// -- observable::flat_map -----------------------------------------------------
/// @relates merger_impl
template
<
class
T
>
struct
merger_input
{
explicit
merger_input
(
observable
<
T
>
in
)
:
in
(
std
::
move
(
in
))
{
// nop
}
/// Stores a handle to the input observable for delayed subscription.
observable
<
T
>
in
;
/// The subscription to this input.
subscription
sub
;
/// Stores received items until the merger can forward them downstream.
std
::
vector
<
T
>
buf
;
};
/// Combines items from any number of observables.
template
<
class
T
>
class
merger_impl
:
public
observable_impl_base
<
T
>
{
public:
// -- member types -----------------------------------------------------------
using
super
=
observable_impl_base
<
T
>
;
using
input_t
=
merger_input
<
T
>
;
using
input_ptr
=
std
::
unique_ptr
<
input_t
>
;
using
input_key
=
size_t
;
using
input_map
=
detail
::
unordered_flat_map
<
input_key
,
input_ptr
>
;
// -- friends ----------------------------------------------------------------
CAF_INTRUSIVE_PTR_FRIENDS
(
merger_impl
)
template
<
class
,
class
,
class
>
friend
class
flow
::
forwarder
;
// -- constructors, destructors, and assignment operators --------------------
explicit
merger_impl
(
coordinator
*
ctx
)
:
super
(
ctx
)
{
// nop
}
// -- implementation of disposable_impl --------------------------------------
void
dispose
()
override
{
for
(
auto
&
kvp
:
inputs_
)
{
auto
&
input
=
*
kvp
.
second
;
if
(
input
.
in
)
{
input
.
in
=
nullptr
;
}
if
(
input
.
sub
)
{
input
.
sub
.
cancel
();
input
.
sub
=
nullptr
;
}
}
inputs_
.
clear
();
term_
.
dispose
();
}
bool
disposed
()
const
noexcept
override
{
return
term_
.
finalized
();
}
// -- implementation of observable<T>::impl ----------------------------------
void
on_request
(
disposable_impl
*
sink
,
size_t
demand
)
override
{
if
(
auto
n
=
term_
.
on_request
(
sink
,
demand
);
n
>
0
)
{
pull
(
n
);
term_
.
push
();
}
}
void
on_cancel
(
disposable_impl
*
sink
)
override
{
if
(
auto
n
=
term_
.
on_cancel
(
sink
);
n
>
0
)
{
pull
(
n
);
term_
.
push
();
}
}
disposable
subscribe
(
observer
<
T
>
sink
)
override
{
// On the first subscribe, we subscribe to our inputs unless the user did
// not add any inputs before that. In that case, we close immediately except
// when running with shutdown_on_last_complete turned off.
if
(
term_
.
idle
()
&&
inputs_
.
empty
()
&&
flags_
.
shutdown_on_last_complete
)
term_
.
close
();
auto
res
=
term_
.
add
(
this
,
sink
);
if
(
res
&&
term_
.
start
())
{
for
(
auto
&
[
key
,
input
]
:
inputs_
)
{
using
fwd_impl
=
forwarder
<
T
,
merger_impl
,
size_t
>
;
auto
fwd
=
make_counted
<
fwd_impl
>
(
this
,
key
);
input
->
in
.
subscribe
(
fwd
->
as_observer
());
}
}
return
res
;
}
// -- dynamic input management -----------------------------------------------
template
<
class
Observable
>
void
add
(
Observable
source
)
{
switch
(
term_
.
state
())
{
case
observable_state
:
:
idle
:
// Only add to the inputs but don't subscribe yet.
emplace
(
std
::
move
(
source
).
as_observable
());
break
;
case
observable_state
:
:
running
:
{
// Add and subscribe.
auto
&
[
key
,
input
]
=
emplace
(
std
::
move
(
source
).
as_observable
());
using
fwd_impl
=
forwarder
<
T
,
merger_impl
,
size_t
>
;
auto
fwd
=
make_counted
<
fwd_impl
>
(
this
,
key
);
input
->
in
.
subscribe
(
fwd
->
as_observer
());
break
;
}
default:
// In any other case, this turns into a no-op.
break
;
}
}
// -- properties -------------------------------------------------------------
size_t
buffered
()
{
return
std
::
accumulate
(
inputs_
.
begin
(),
inputs_
.
end
(),
size_t
{
0
},
[](
size_t
tmp
,
const
merger_input
<
T
>&
in
)
{
return
tmp
+
in
.
buf
.
size
();
});
}
bool
shutdown_on_last_complete
()
const
noexcept
{
return
flags_
.
shutdown_on_last_complete
;
}
void
shutdown_on_last_complete
(
bool
new_value
)
noexcept
{
flags_
.
shutdown_on_last_complete
=
new_value
;
if
(
new_value
&&
inputs_
.
empty
())
term_
.
fin
();
}
private:
typename
input_map
::
value_type
&
emplace
(
observable
<
T
>
source
)
{
auto
&
vec
=
inputs_
.
container
();
vec
.
emplace_back
(
next_key_
++
,
std
::
make_unique
<
input_t
>
(
std
::
move
(
source
)));
return
vec
.
back
();
}
void
fwd_on_subscribe
(
input_key
key
,
subscription
sub
)
{
if
(
!
term_
.
finalized
())
{
if
(
auto
i
=
inputs_
.
find
(
key
);
i
!=
inputs_
.
end
())
{
auto
&
in
=
*
i
->
second
;
sub
.
request
(
max_pending_
);
in
.
sub
=
std
::
move
(
sub
);
}
else
{
sub
.
cancel
();
}
}
else
{
sub
.
cancel
();
}
}
void
drop_if_empty
(
typename
input_map
::
iterator
i
)
{
auto
&
in
=
*
i
->
second
;
if
(
in
.
buf
.
empty
())
{
inputs_
.
erase
(
i
);
if
(
inputs_
.
empty
()
&&
flags_
.
shutdown_on_last_complete
)
term_
.
fin
();
}
else
{
in
.
in
=
nullptr
;
in
.
sub
=
nullptr
;
}
}
void
fwd_on_complete
(
input_key
key
)
{
if
(
auto
i
=
inputs_
.
find
(
key
);
i
!=
inputs_
.
end
())
drop_if_empty
(
i
);
}
void
fwd_on_error
(
input_key
key
,
const
error
&
what
)
{
if
(
auto
i
=
inputs_
.
find
(
key
);
i
!=
inputs_
.
end
())
{
if
(
!
term_
.
err
())
{
term_
.
err
(
what
);
if
(
flags_
.
delay_error
)
{
drop_if_empty
(
i
);
}
else
{
auto
&
in
=
*
i
->
second
;
if
(
!
in
.
buf
.
empty
())
{
for
(
auto
&
item
:
in
.
buf
)
term_
.
on_next
(
item
);
}
term_
.
fin
();
for
(
auto
j
=
inputs_
.
begin
();
j
!=
inputs_
.
end
();
++
j
)
if
(
j
!=
i
&&
j
->
second
->
sub
)
j
->
second
->
sub
.
cancel
();
inputs_
.
clear
();
}
}
else
{
drop_if_empty
(
i
);
}
}
}
void
fwd_on_next
(
input_key
key
,
const
T
&
item
)
{
if
(
auto
i
=
inputs_
.
find
(
key
);
i
!=
inputs_
.
end
())
{
auto
&
in
=
*
i
->
second
;
if
(
!
term_
.
finalized
())
{
if
(
term_
.
min_demand
()
>
0
)
{
term_
.
on_next
(
item
);
term_
.
push
();
if
(
in
.
sub
)
in
.
sub
.
request
(
1
);
}
else
{
in
.
buf
.
emplace_back
(
item
);
}
}
}
}
void
pull
(
size_t
n
)
{
// Must not be re-entered. Any on_request call must use the event loop.
CAF_ASSERT
(
!
pulling_
);
if
(
inputs_
.
empty
())
{
if
(
flags_
.
shutdown_on_last_complete
)
term_
.
fin
();
return
;
}
CAF_DEBUG_STMT
(
pulling_
=
true
);
auto
&
in_vec
=
inputs_
.
container
();
for
(
size_t
i
=
0
;
n
>
0
&&
i
<
inputs_
.
size
();
++
i
)
{
auto
index
=
(
pos_
+
1
)
%
inputs_
.
size
();
auto
&
in
=
*
in_vec
[
index
].
second
;
if
(
auto
m
=
std
::
min
(
in
.
buf
.
size
(),
n
);
m
>
0
)
{
n
-=
m
;
auto
items
=
make_span
(
in
.
buf
.
data
(),
m
);
for
(
auto
&
item
:
items
)
term_
.
on_next
(
item
);
in
.
buf
.
erase
(
in
.
buf
.
begin
(),
in
.
buf
.
begin
()
+
m
);
if
(
in
.
sub
)
{
in
.
sub
.
request
(
m
);
pos_
=
index
;
}
else
if
(
!
in
.
in
&&
in
.
buf
.
empty
())
{
in_vec
.
erase
(
in_vec
.
begin
()
+
index
);
if
(
in_vec
.
empty
())
{
if
(
flags_
.
shutdown_on_last_complete
)
term_
.
fin
();
CAF_DEBUG_STMT
(
pulling_
=
false
);
return
;
}
}
else
{
pos_
=
index
;
}
}
}
CAF_DEBUG_STMT
(
pulling_
=
false
);
}
struct
flags_t
{
bool
delay_error
:
1
;
bool
shutdown_on_last_complete
:
1
;
flags_t
()
:
delay_error
(
false
),
shutdown_on_last_complete
(
true
)
{
// nop
}
};
/// Fine-tunes the behavior of the merger.
flags_t
flags_
;
/// Configures how many items we buffer per input.
size_t
max_pending_
=
defaults
::
flow
::
buffer_size
;
/// Stores the last round-robin read position.
size_t
pos_
=
0
;
/// Associates inputs with ascending keys.
input_map
inputs_
;
/// Stores the last round-robin read position.
size_t
next_key_
=
0
;
/// Pushes items to subscribed observers.
broadcast_step
<
T
>
term_
;
#ifdef CAF_ENABLE_RUNTIME_CHECKS
/// Protect against re-entering `pull`.
bool
pulling_
=
false
;
#endif
};
template
<
class
T
>
using
merger_impl_ptr
=
intrusive_ptr
<
merger_impl
<
T
>>
;
template
<
class
T
,
class
F
>
class
flat_map_observer_impl
:
public
ref_counted
,
public
observer_impl
<
T
>
{
public:
using
mapped_type
=
decltype
((
std
::
declval
<
F
&>
())(
std
::
declval
<
const
T
&>
()));
using
inner_type
=
typename
mapped_type
::
output_type
;
CAF_INTRUSIVE_PTR_FRIENDS
(
flat_map_observer_impl
)
flat_map_observer_impl
(
coordinator
*
ctx
,
F
f
)
:
ctx_
(
ctx
),
map_
(
std
::
move
(
f
))
{
merger_
.
emplace
(
ctx
);
merger_
->
shutdown_on_last_complete
(
false
);
}
void
dispose
()
override
{
if
(
sub_
)
{
sub_
.
cancel
();
sub_
=
nullptr
;
merger_
->
shutdown_on_last_complete
(
true
);
merger_
=
nullptr
;
}
}
bool
disposed
()
const
noexcept
override
{
return
merger_
!=
nullptr
;
}
void
ref_disposable
()
const
noexcept
final
{
this
->
ref
();
}
void
deref_disposable
()
const
noexcept
final
{
this
->
deref
();
}
void
on_complete
()
override
{
if
(
sub_
)
{
sub_
=
nullptr
;
merger_
->
shutdown_on_last_complete
(
true
);
merger_
=
nullptr
;
}
}
void
on_error
(
const
error
&
what
)
override
{
if
(
sub_
)
{
sub_
=
nullptr
;
merger_
->
shutdown_on_last_complete
(
true
);
auto
obs
=
make_observable
<
observable_error_impl
<
inner_type
>>
(
ctx_
,
what
);
merger_
->
add
(
std
::
move
(
obs
));
merger_
=
nullptr
;
}
}
void
on_subscribe
(
subscription
sub
)
override
{
if
(
!
sub_
&&
merger_
)
{
sub_
=
std
::
move
(
sub
);
sub_
.
request
(
10
);
}
else
{
sub
.
cancel
();
}
}
void
on_next
(
const
T
&
src
)
override
{
if
(
sub_
)
{
merger_
->
add
(
map_
(
src
).
as_observable
());
sub_
.
request
(
1
);
}
}
observable
<
inner_type
>
merger
()
{
return
observable
<
inner_type
>
{
merger_
};
}
auto
&
merger_ptr
()
{
return
merger_
;
}
private:
coordinator
*
ctx_
;
subscription
sub_
;
F
map_
;
intrusive_ptr
<
merger_impl
<
inner_type
>>
merger_
;
};
template
<
class
T
>
template
<
class
F
>
auto
observable
<
T
>::
flat_map
(
F
f
)
{
using
f_res
=
decltype
(
f
(
std
::
declval
<
const
T
&>
()));
static_assert
(
is_observable_v
<
f_res
>
,
"mapping functions must return an observable"
);
using
impl_t
=
flat_map_observer_impl
<
T
,
F
>
;
auto
obs
=
make_counted
<
impl_t
>
(
pimpl_
->
ctx
(),
std
::
move
(
f
));
pimpl_
->
subscribe
(
obs
->
as_observer
());
return
obs
->
merger
();
}
// -- observable::flat_map_optional --------------------------------------------
template
<
class
T
>
template
<
class
F
>
transformation
<
flat_map_optional_step
<
F
>>
observable
<
T
>::
flat_map_optional
(
F
f
)
{
using
step_type
=
flat_map_optional_step
<
F
>
;
static_assert
(
std
::
is_same_v
<
typename
step_type
::
input_type
,
T
>
,
"flat_map_optional function does not match the input type"
);
return
{
*
this
,
std
::
forward_as_tuple
(
step_type
{
std
::
move
(
f
)})};
}
// -- observable::concat_map ---------------------------------------------------
/// Combines items from any number of observables.
template
<
class
T
>
class
concat_impl
:
public
observable_impl_base
<
T
>
{
public:
// -- member types -----------------------------------------------------------
using
super
=
observable_impl_base
<
T
>
;
using
input_key
=
size_t
;
// -- friends ----------------------------------------------------------------
CAF_INTRUSIVE_PTR_FRIENDS
(
concat_impl
)
template
<
class
,
class
,
class
>
friend
class
flow
::
forwarder
;
// -- constructors, destructors, and assignment operators --------------------
explicit
concat_impl
(
coordinator
*
ctx
)
:
super
(
ctx
)
{
// nop
}
// -- implementation of disposable_impl --------------------------------------
void
dispose
()
override
{
if
(
sub_
)
sub_
.
cancel
();
inputs_
.
clear
();
term_
.
dispose
();
}
bool
disposed
()
const
noexcept
override
{
return
term_
.
finalized
();
}
// -- implementation of subscription::listener -------------------------------
void
on_request
(
disposable_impl
*
sink
,
size_t
demand
)
override
{
if
(
auto
n
=
term_
.
on_request
(
sink
,
demand
);
n
>
0
)
{
in_flight_
+=
n
;
if
(
sub_
)
sub_
.
request
(
n
);
}
}
void
on_cancel
(
disposable_impl
*
sink
)
override
{
if
(
auto
n
=
term_
.
on_cancel
(
sink
);
n
>
0
)
{
in_flight_
+=
n
;
if
(
sub_
)
sub_
.
request
(
n
);
}
}
// -- implementation of observable<T>::impl ----------------------------------
disposable
subscribe
(
observer
<
T
>
sink
)
override
{
// On the first subscribe, we subscribe to our inputs unless the user did
// not add any inputs before that. In that case, we close immediately except
// when running with shutdown_on_last_complete turned off.
if
(
term_
.
idle
()
&&
inputs_
.
empty
()
&&
flags_
.
shutdown_on_last_complete
)
term_
.
close
();
auto
res
=
term_
.
add
(
this
,
sink
);
if
(
res
&&
term_
.
start
()
&&
!
inputs_
.
empty
())
subscribe_next
();
return
res
;
}
// -- dynamic input management -----------------------------------------------
template
<
class
Observable
>
void
add
(
Observable
source
)
{
switch
(
term_
.
state
())
{
case
observable_state
:
:
idle
:
inputs_
.
emplace_back
(
std
::
move
(
source
).
as_observable
());
break
;
case
observable_state
:
:
running
:
inputs_
.
emplace_back
(
std
::
move
(
source
).
as_observable
());
if
(
inputs_
.
size
()
==
1
)
subscribe_next
();
break
;
default:
// In any other case, this turns into a no-op.
break
;
}
}
// -- properties -------------------------------------------------------------
bool
shutdown_on_last_complete
()
const
noexcept
{
return
flags_
.
shutdown_on_last_complete
;
}
void
shutdown_on_last_complete
(
bool
new_value
)
noexcept
{
flags_
.
shutdown_on_last_complete
=
new_value
;
if
(
new_value
&&
inputs_
.
empty
())
term_
.
fin
();
}
private:
void
subscribe_next
()
{
CAF_ASSERT
(
!
inputs_
.
empty
());
CAF_ASSERT
(
!
sub_
);
auto
input
=
inputs_
.
front
();
++
in_key_
;
using
fwd_impl
=
forwarder
<
T
,
concat_impl
,
size_t
>
;
auto
fwd
=
make_counted
<
fwd_impl
>
(
this
,
in_key_
);
input
.
subscribe
(
fwd
->
as_observer
());
}
void
fwd_on_subscribe
(
input_key
key
,
subscription
sub
)
{
if
(
in_key_
==
key
&&
term_
.
active
())
{
sub_
=
std
::
move
(
sub
);
if
(
in_flight_
>
0
)
sub_
.
request
(
in_flight_
);
}
else
{
sub
.
cancel
();
}
}
void
fwd_on_complete
(
input_key
key
)
{
if
(
in_key_
==
key
)
{
inputs_
.
erase
(
inputs_
.
begin
());
sub_
=
nullptr
;
if
(
!
inputs_
.
empty
())
{
subscribe_next
();
}
else
if
(
flags_
.
shutdown_on_last_complete
)
{
term_
.
fin
();
}
}
}
void
fwd_on_error
(
input_key
key
,
const
error
&
what
)
{
if
(
in_key_
==
key
)
{
if
(
!
flags_
.
delay_error
)
{
term_
.
on_error
(
what
);
sub_
=
nullptr
;
inputs_
.
clear
();
}
else
if
(
!
term_
.
err
())
{
term_
.
err
(
what
);
fwd_on_complete
(
key
);
}
else
{
fwd_on_complete
(
key
);
}
}
}
void
fwd_on_next
(
input_key
key
,
const
T
&
item
)
{
if
(
in_key_
==
key
&&
!
term_
.
finalized
())
{
CAF_ASSERT
(
in_flight_
>=
0
);
--
in_flight_
;
term_
.
on_next
(
item
);
term_
.
push
();
}
}
struct
flags_t
{
bool
delay_error
:
1
;
bool
shutdown_on_last_complete
:
1
;
flags_t
()
:
delay_error
(
false
),
shutdown_on_last_complete
(
true
)
{
// nop
}
};
/// Fine-tunes the behavior of the concat.
flags_t
flags_
;
/// Stores our input sources. The first input is active (subscribed to) while
/// the others are pending (not subscribed to).
std
::
vector
<
observable
<
T
>>
inputs_
;
/// Our currently active subscription.
subscription
sub_
;
/// Identifies the forwarder.
input_key
in_key_
=
0
;
/// Stores how much demand we have left. When switching to a new input, we
/// pass any demand unused by the previous input to the new one.
size_t
in_flight_
=
0
;
/// Pushes items to subscribed observers.
broadcast_step
<
T
>
term_
;
};
template
<
class
T
,
class
F
>
class
concat_map_observer_impl
:
public
ref_counted
,
public
observer_impl
<
T
>
{
public:
using
mapped_type
=
decltype
((
std
::
declval
<
F
&>
())(
std
::
declval
<
const
T
&>
()));
using
inner_type
=
typename
mapped_type
::
output_type
;
CAF_INTRUSIVE_PTR_FRIENDS
(
concat_map_observer_impl
)
concat_map_observer_impl
(
coordinator
*
ctx
,
F
f
)
:
ctx_
(
ctx
),
map_
(
std
::
move
(
f
))
{
concat_
.
emplace
(
ctx
);
concat_
->
shutdown_on_last_complete
(
false
);
}
void
dispose
()
override
{
if
(
sub_
)
{
sub_
.
cancel
();
sub_
=
nullptr
;
concat_
->
shutdown_on_last_complete
(
true
);
concat_
=
nullptr
;
}
}
bool
disposed
()
const
noexcept
override
{
return
concat_
!=
nullptr
;
}
void
ref_disposable
()
const
noexcept
final
{
this
->
ref
();
}
void
deref_disposable
()
const
noexcept
final
{
this
->
deref
();
}
void
on_complete
()
override
{
if
(
sub_
)
{
sub_
=
nullptr
;
concat_
->
shutdown_on_last_complete
(
true
);
concat_
=
nullptr
;
}
}
void
on_error
(
const
error
&
what
)
override
{
if
(
sub_
)
{
sub_
=
nullptr
;
concat_
->
shutdown_on_last_complete
(
true
);
auto
obs
=
make_observable
<
observable_error_impl
<
inner_type
>>
(
ctx_
,
what
);
concat_
->
add
(
std
::
move
(
obs
));
concat_
=
nullptr
;
}
}
void
on_subscribe
(
subscription
sub
)
override
{
if
(
!
sub_
&&
concat_
)
{
sub_
=
std
::
move
(
sub
);
sub_
.
request
(
10
);
}
else
{
sub
.
cancel
();
}
}
void
on_next
(
const
T
&
src
)
override
{
if
(
sub_
)
{
concat_
->
add
(
map_
(
src
).
as_observable
());
sub_
.
request
(
1
);
}
}
observable
<
inner_type
>
concat
()
{
return
observable
<
inner_type
>
{
concat_
};
}
auto
&
concat_ptr
()
{
return
concat_
;
}
private:
coordinator
*
ctx_
;
subscription
sub_
;
F
map_
;
intrusive_ptr
<
concat_impl
<
inner_type
>>
concat_
;
};
template
<
class
T
>
template
<
class
T
>
template
<
class
F
>
template
<
class
Predicate
>
auto
observable
<
T
>::
concat_map
(
F
f
)
{
transformation
<
take_while_step
<
Predicate
>>
using
f_res
=
decltype
(
f
(
std
::
declval
<
const
T
&>
()));
observable
<
T
>::
take_while
(
Predicate
predicate
)
{
static_assert
(
is_observable_v
<
f_res
>
,
using
step_type
=
take_while_step
<
Predicate
>
;
"mapping functions must return an observable"
);
static_assert
(
std
::
is_same_v
<
typename
step_type
::
input_type
,
T
>
,
using
impl_t
=
concat_map_observer_impl
<
T
,
F
>
;
"predicate does not match the input type"
);
auto
obs
=
make_counted
<
impl_t
>
(
pimpl_
->
ctx
(),
std
::
move
(
f
));
return
{
*
this
,
std
::
forward_as_tuple
(
step_type
{
std
::
move
(
predicate
)})};
pimpl_
->
subscribe
(
obs
->
as_observer
());
return
obs
->
concat
();
}
}
// -- observable::
prefix_and_tail
----------------------------------------------
// -- observable::
sum ------------
----------------------------------------------
template
<
class
T
>
template
<
class
T
>
class
prefix_and_tail_observable_impl
final
template
<
class
Reducer
>
:
public
ref_counted
,
transformation
<
reduce_step
<
T
,
Reducer
>>
public
subscription
::
listener
,
observable
<
T
>::
reduce
(
T
init
,
Reducer
reducer
)
{
public
observable_impl
<
T
>
,
// For the forwarding to the 'tail'.
return
{
*
this
,
reduce_step
<
T
,
Reducer
>
{
init
,
reducer
}};
public
processor_impl
<
T
,
cow_tuple
<
std
::
vector
<
T
>
,
observable
<
T
>>>
{
}
public:
// -- member types -----------------------------------------------------------
using
in_t
=
T
;
using
out_t
=
cow_tuple
<
std
::
vector
<
T
>
,
observable
<
T
>>
;
using
in_obs_t
=
observable_impl
<
in_t
>
;
using
out_obs_t
=
observable_impl
<
out_t
>
;
// -- observable::distinct -----------------------------------------------------
// -- constructors, destructors, and assignment operators --------------------
template
<
class
T
>
transformation
<
distinct_step
<
T
>>
observable
<
T
>::
distinct
()
{
return
{
*
this
,
distinct_step
<
T
>
{}};
}
prefix_and_tail_observable_impl
(
coordinator
*
ctx
,
size_t
prefix_size
)
// -- observable::map ----------------------------------------------------------
:
ctx_
(
ctx
),
prefix_size_
(
prefix_size
)
{
// nop
}
// -- friends ----------------------------------------------------------------
template
<
class
T
>
template
<
class
F
>
CAF_INTRUSIVE_PTR_FRIENDS
(
prefix_and_tail_observable_impl
)
transformation
<
map_step
<
F
>>
observable
<
T
>::
map
(
F
f
)
{
using
step_type
=
map_step
<
F
>
;
// -- implementation of disposable_impl --------------------------------------
static_assert
(
std
::
is_same_v
<
typename
step_type
::
input_type
,
T
>
,
"map function does not match the input type"
);
void
dispose
()
override
{
return
{
*
this
,
std
::
forward_as_tuple
(
step_type
{
std
::
move
(
f
)})};
if
(
sub_
)
{
}
sub_
.
cancel
();
sub_
=
nullptr
;
}
if
(
obs_
)
{
obs_
.
on_complete
();
obs_
=
nullptr
;
}
if
(
tail_
)
{
tail_
.
on_complete
();
tail_
=
nullptr
;
}
}
bool
disposed
()
const
noexcept
override
{
// -- observable::for_each -----------------------------------------------------
return
!
sub_
&&
!
obs_
&&
!
tail_
;
}
void
ref_disposable
()
const
noexcept
final
{
template
<
class
T
>
this
->
ref
();
template
<
class
OnNext
>
}
disposable
observable
<
T
>::
for_each
(
OnNext
on_next
)
{
auto
obs
=
make_observer
(
std
::
move
(
on_next
));
return
subscribe
(
std
::
move
(
obs
));
}
void
deref_disposable
()
const
noexcept
final
{
template
<
class
T
>
this
->
deref
();
template
<
class
OnNext
,
class
OnError
>
}
disposable
observable
<
T
>::
for_each
(
OnNext
on_next
,
OnError
on_error
)
{
auto
obs
=
make_observer
(
std
::
move
(
on_next
),
std
::
move
(
on_error
));
return
subscribe
(
std
::
move
(
obs
));
}
// -- implementation of observable<T>::impl ----------------------------------
template
<
class
T
>
template
<
class
OnNext
,
class
OnError
,
class
OnComplete
>
disposable
observable
<
T
>::
for_each
(
OnNext
on_next
,
OnError
on_error
,
OnComplete
on_complete
)
{
auto
obs
=
make_observer
(
std
::
move
(
on_next
),
std
::
move
(
on_error
),
std
::
move
(
on_complete
));
return
subscribe
(
std
::
move
(
obs
));
}
coordinator
*
ctx
()
const
noexcept
final
{
// -- observable::merge --------------------------------------------------------
return
ctx_
;
}
// -- implementation of observable<in_t>::impl -------------------------------
template
<
class
T
>
template
<
class
...
Inputs
>
disposable
subscribe
(
observer
<
in_t
>
sink
)
override
{
auto
observable
<
T
>::
merge
(
Inputs
&&
...
xs
)
{
if
(
sink
.
ptr
()
==
tail_
.
ptr
())
{
if
constexpr
(
is_observable_v
<
output_type
>
)
{
using
sub_t
=
subscription
::
default_impl
;
using
value_t
=
output_type_t
<
output_type
>
;
sink
.
on_subscribe
(
sub_t
::
make_unsafe
(
ctx_
,
this
,
sink
.
ptr
()));
using
impl_t
=
op
::
merge
<
value_t
>
;
return
sink
.
as_disposable
();
return
make_observable
<
impl_t
>
(
ctx
(),
*
this
,
std
::
forward
<
Inputs
>
(
xs
)...);
}
else
{
}
else
{
sink
.
on_error
(
make_error
(
sec
::
invalid_observable
));
static_assert
(
return
disposable
{};
sizeof
...(
Inputs
)
>
0
,
}
"merge without arguments expects this observable to emit observables"
);
using
impl_t
=
op
::
merge
<
output_type
>
;
return
make_observable
<
impl_t
>
(
ctx
(),
*
this
,
std
::
forward
<
Inputs
>
(
xs
)...);
}
}
}
// -- implementation of observable<out_t>::impl ------------------------------
// -- observable::flat_map -----------------------------------------------------
disposable
subscribe
(
observer
<
out_t
>
sink
)
override
{
obs_
=
sink
;
using
sub_t
=
subscription
::
default_impl
;
sink
.
on_subscribe
(
sub_t
::
make_unsafe
(
ctx_
,
this
,
sink
.
ptr
()));
return
sink
.
as_disposable
();
}
// -- implementation of subscription::listener -------------------------------
template
<
class
T
>
template
<
class
F
>
void
on_request
(
disposable_impl
*
sink
,
size_t
n
)
override
{
auto
observable
<
T
>::
flat_map
(
F
f
)
{
if
(
sink
==
obs_
.
ptr
())
{
using
res_t
=
decltype
(
f
(
std
::
declval
<
const
output_type
&>
()));
if
(
sub_
&&
!
requested_prefix_
)
{
if
constexpr
(
is_observable_v
<
res_t
>
)
{
requested_prefix_
=
true
;
return
map
([
fn
=
std
::
move
(
f
)](
const
output_type
&
x
)
mutable
{
sub_
.
request
(
prefix_size_
);
return
fn
(
x
).
as_observable
();
}
})
}
else
if
(
sink
==
tail_
.
ptr
())
{
.
merge
();
if
(
sub_
)
}
else
if
constexpr
(
detail
::
is_optional_v
<
res_t
>
)
{
sub_
.
request
(
n
);
return
map
([
fn
=
std
::
move
(
f
)](
const
output_type
&
x
)
mutable
{
}
return
fn
(
x
);
})
.
filter
([](
const
res_t
&
x
)
{
return
x
.
has_value
();
})
.
map
([](
const
res_t
&
x
)
{
return
*
x
;
});
}
else
{
// Here, we dispatch to concat() instead of merging the containers. Merged
// output is probably not what anyone would expect and since the values are
// all available immediately, there is no good reason to mess up the emitted
// order of values.
static_assert
(
detail
::
is_iterable_v
<
res_t
>
);
return
map
([
cptr
=
ctx
(),
fn
=
std
::
move
(
f
)](
const
output_type
&
x
)
mutable
{
return
cptr
->
make_observable
().
from_container
(
fn
(
x
));
})
.
concat
();
}
}
}
void
on_cancel
(
disposable_impl
*
sink
)
override
{
// -- observable::flat_map_optional --------------------------------------------
if
(
sink
==
obs_
.
ptr
())
{
// Only has an effect when canceling immediately. Otherwise, we forward to
// tail_ and the original observer no longer is of any interest since it
// receives at most one item anyways.
if
(
sub_
&&
!
tail_
)
{
sub_
.
cancel
();
sub_
=
nullptr
;
}
}
else
if
(
sink
==
tail_
.
ptr
())
{
if
(
sub_
)
{
sub_
.
cancel
();
sub_
=
nullptr
;
}
}
}
// -- implementation of observer<in_t>::impl ---------------------------------
template
<
class
T
>
template
<
class
F
>
transformation
<
flat_map_optional_step
<
F
>>
observable
<
T
>::
flat_map_optional
(
F
f
)
{
using
step_type
=
flat_map_optional_step
<
F
>
;
static_assert
(
std
::
is_same_v
<
typename
step_type
::
input_type
,
T
>
,
"flat_map_optional function does not match the input type"
);
return
{
*
this
,
std
::
forward_as_tuple
(
step_type
{
std
::
move
(
f
)})};
}
void
on_subscribe
(
subscription
sub
)
override
{
// -- observable::concat -------------------------------------------------------
if
(
!
had_subscriber_
)
{
had_subscriber_
=
true
;
sub_
=
std
::
move
(
sub
);
}
else
{
sub
.
cancel
();
}
}
void
on_next
(
const
in_t
&
item
)
override
{
template
<
class
T
>
if
(
tail_
)
{
template
<
class
...
Inputs
>
tail_
.
on_next
(
item
);
auto
observable
<
T
>::
concat
(
Inputs
&&
...
xs
)
{
}
else
if
(
obs_
)
{
if
constexpr
(
is_observable_v
<
output_type
>
)
{
prefix_
.
emplace_back
(
item
);
using
value_t
=
output_type_t
<
output_type
>
;
if
(
prefix_
.
size
()
>=
prefix_size_
)
{
using
impl_t
=
op
::
concat
<
value_t
>
;
auto
tptr
=
make_broadcaster_impl
<
in_t
>
(
ctx_
);
return
make_observable
<
impl_t
>
(
ctx
(),
*
this
,
std
::
forward
<
Inputs
>
(
xs
)...);
tail_
=
tptr
->
as_observer
();
}
else
{
static_cast
<
observable_impl
<
in_t
>*>
(
this
)
->
subscribe
(
tail_
);
static_assert
(
auto
tup
=
make_cow_tuple
(
std
::
move
(
prefix_
),
tptr
->
as_observable
());
sizeof
...(
Inputs
)
>
0
,
obs_
.
on_next
(
tup
);
"merge without arguments expects this observable to emit observables"
);
obs_
.
on_complete
();
using
impl_t
=
op
::
concat
<
output_type
>
;
obs_
=
nullptr
;
return
make_observable
<
impl_t
>
(
ctx
(),
*
this
,
std
::
forward
<
Inputs
>
(
xs
)...);
}
}
}
}
}
void
on_complete
()
override
{
// -- observable::concat_map ---------------------------------------------------
sub_
=
nullptr
;
if
(
obs_
)
{
obs_
.
on_complete
();
obs_
=
nullptr
;
}
if
(
tail_
)
{
tail_
.
on_complete
();
tail_
=
nullptr
;
}
}
void
on_error
(
const
error
&
what
)
override
{
template
<
class
T
>
sub_
=
nullptr
;
template
<
class
F
>
if
(
obs_
)
{
auto
observable
<
T
>::
concat_map
(
F
f
)
{
obs_
.
on_error
(
what
);
using
res_t
=
decltype
(
f
(
std
::
declval
<
const
output_type
&>
()));
obs_
=
nullptr
;
if
constexpr
(
is_observable_v
<
res_t
>
)
{
}
return
map
([
fn
=
std
::
move
(
f
)](
const
output_type
&
x
)
mutable
{
if
(
tail_
)
{
return
fn
(
x
).
as_observable
();
tail_
.
on_error
(
what
);
})
tail_
=
nullptr
;
.
concat
();
}
}
else
if
constexpr
(
detail
::
is_optional_v
<
res_t
>
)
{
return
map
([
fn
=
std
::
move
(
f
)](
const
output_type
&
x
)
mutable
{
return
fn
(
x
);
})
.
filter
([](
const
res_t
&
x
)
{
return
x
.
has_value
();
})
.
map
([](
const
res_t
&
x
)
{
return
*
x
;
});
}
else
{
static_assert
(
detail
::
is_iterable_v
<
res_t
>
);
return
map
([
cptr
=
ctx
(),
fn
=
std
::
move
(
f
)](
const
output_type
&
x
)
mutable
{
return
cptr
->
make_observable
().
from_container
(
fn
(
x
));
})
.
concat
();
}
}
}
private:
// -- observable::prefix_and_tail ----------------------------------------------
/// Our context.
coordinator
*
ctx_
;
/// Subscription to the input data.
subscription
sub_
;
/// Handle for the observer that gets the prefix + tail tuple.
observer
<
out_t
>
obs_
;
/// Handle to the tail for forwarding any data after the prefix.
observer
<
in_t
>
tail_
;
/// Makes sure we only respect the first subscriber.
bool
had_subscriber_
=
false
;
/// Stores whether we have requested the prefix;
bool
requested_prefix_
=
false
;
/// Buffer for storing the prefix elements.
std
::
vector
<
in_t
>
prefix_
;
/// User-defined size of the prefix.
size_t
prefix_size_
;
};
template
<
class
T
>
template
<
class
T
>
observable
<
cow_tuple
<
std
::
vector
<
T
>
,
observable
<
T
>>>
observable
<
cow_tuple
<
std
::
vector
<
T
>
,
observable
<
T
>>>
observable
<
T
>::
prefix_and_tail
(
size_t
prefix_size
)
{
observable
<
T
>::
prefix_and_tail
(
size_t
prefix_size
)
{
using
impl_t
=
prefix_and_tail_observable_impl
<
T
>
;
using
impl_t
=
op
::
prefix_and_tail
<
T
>
;
using
out_t
=
cow_tuple
<
std
::
vector
<
T
>
,
observable
<
T
>>
;
return
make_observable
<
impl_t
>
(
ctx
(),
pimpl_
,
prefix_size
);
auto
obs
=
make_counted
<
impl_t
>
(
pimpl_
->
ctx
(),
prefix_size
);
pimpl_
->
subscribe
(
obs
->
as_observer
());
return
static_cast
<
observable_impl
<
out_t
>*>
(
obs
.
get
())
->
as_observable
();
}
}
// -- observable::prefix_and_tail ----------------------------------------------
// -- observable::prefix_and_tail ----------------------------------------------
...
@@ -1831,157 +464,6 @@ observable<cow_tuple<T, observable<T>>> observable<T>::head_and_tail() {
...
@@ -1831,157 +464,6 @@ observable<cow_tuple<T, observable<T>>> observable<T>::head_and_tail() {
/// Reads from an observable buffer and emits the consumed items.
/// Reads from an observable buffer and emits the consumed items.
/// @note Only supports a single observer.
/// @note Only supports a single observer.
template
<
class
Buffer
>
class
observable_buffer_impl
:
public
observable_impl_base
<
typename
Buffer
::
value_type
>
,
public
async
::
consumer
{
public:
// -- member types -----------------------------------------------------------
using
value_type
=
typename
Buffer
::
value_type
;
using
buffer_ptr
=
intrusive_ptr
<
Buffer
>
;
using
super
=
observable_impl_base
<
value_type
>
;
// -- friends ----------------------------------------------------------------
CAF_INTRUSIVE_PTR_FRIENDS
(
observable_buffer_impl
)
// -- constructors, destructors, and assignment operators --------------------
observable_buffer_impl
(
coordinator
*
ctx
,
buffer_ptr
buf
)
:
super
(
ctx
),
buf_
(
buf
)
{
// Unlike regular observables, we need a strong reference to the context.
// Otherwise, the buffer might call schedule_fn on a destroyed object.
this
->
ctx
()
->
ref_coordinator
();
}
~
observable_buffer_impl
()
{
if
(
buf_
)
buf_
->
cancel
();
this
->
ctx
()
->
deref_coordinator
();
}
// -- implementation of disposable_impl --------------------------------------
void
dispose
()
override
{
CAF_LOG_TRACE
(
""
);
if
(
buf_
)
{
buf_
->
cancel
();
buf_
=
nullptr
;
if
(
dst_
)
{
dst_
.
on_complete
();
dst_
=
nullptr
;
}
}
}
bool
disposed
()
const
noexcept
override
{
return
buf_
==
nullptr
;
}
// -- implementation of observable<T>::impl ----------------------------------
void
on_request
(
disposable_impl
*
,
size_t
n
)
override
{
CAF_LOG_TRACE
(
CAF_ARG
(
n
));
demand_
+=
n
;
if
(
demand_
==
n
)
pull
();
}
void
on_cancel
(
disposable_impl
*
)
override
{
CAF_LOG_TRACE
(
""
);
dst_
=
nullptr
;
dispose
();
}
disposable
subscribe
(
observer
<
value_type
>
what
)
override
{
CAF_LOG_TRACE
(
""
);
if
(
buf_
&&
!
dst_
)
{
CAF_LOG_DEBUG
(
"add destination"
);
dst_
=
std
::
move
(
what
);
return
super
::
do_subscribe
(
dst_
.
ptr
());
}
else
{
CAF_LOG_DEBUG
(
"already have a destination"
);
auto
err
=
make_error
(
sec
::
cannot_add_upstream
,
"observable buffers support only one observer"
);
what
.
on_error
(
err
);
return
disposable
{};
}
}
// -- implementation of async::consumer: these may get called concurrently ---
void
on_producer_ready
()
override
{
// nop
}
void
on_producer_wakeup
()
override
{
CAF_LOG_TRACE
(
""
);
this
->
ctx
()
->
schedule_fn
([
ptr
{
strong_ptr
()}]
{
CAF_LOG_TRACE
(
""
);
ptr
->
pull
();
});
}
void
ref_consumer
()
const
noexcept
override
{
this
->
ref
();
}
void
deref_consumer
()
const
noexcept
override
{
this
->
deref
();
}
protected:
coordinator
*
ctx_
;
private:
void
pull
()
{
CAF_LOG_TRACE
(
""
);
if
(
!
buf_
||
pulling_
||
!
dst_
)
return
;
pulling_
=
true
;
struct
decorator
{
size_t
*
demand
;
typename
observer
<
value_type
>::
impl
*
dst
;
void
on_next
(
const
value_type
&
item
)
{
CAF_LOG_TRACE
(
CAF_ARG
(
item
));
CAF_ASSERT
(
*
demand
>
0
);
--*
demand
;
dst
->
on_next
(
item
);
}
void
on_complete
()
{
CAF_LOG_TRACE
(
""
);
dst
->
on_complete
();
}
void
on_error
(
const
error
&
what
)
{
CAF_LOG_TRACE
(
CAF_ARG
(
what
));
dst
->
on_error
(
what
);
}
};
decorator
dst
{
&
demand_
,
dst_
.
ptr
()};
if
(
!
buf_
->
pull
(
async
::
prioritize_errors
,
demand_
,
dst
).
first
)
{
buf_
=
nullptr
;
dst_
=
nullptr
;
}
pulling_
=
false
;
}
intrusive_ptr
<
observable_buffer_impl
>
strong_ptr
()
{
return
{
this
};
}
buffer_ptr
buf_
;
/// Stores a pointer to the target observer running on `remote_ctx_`.
observer
<
value_type
>
dst_
;
bool
pulling_
=
false
;
size_t
demand_
=
0
;
};
template
<
class
T
>
template
<
class
T
>
async
::
consumer_resource
<
T
>
async
::
consumer_resource
<
T
>
observable
<
T
>::
to_resource
(
size_t
buffer_size
,
size_t
min_request_size
)
{
observable
<
T
>::
to_resource
(
size_t
buffer_size
,
size_t
min_request_size
)
{
...
@@ -1998,13 +480,10 @@ observable<T>::to_resource(size_t buffer_size, size_t min_request_size) {
...
@@ -1998,13 +480,10 @@ observable<T>::to_resource(size_t buffer_size, size_t min_request_size) {
template
<
class
T
>
template
<
class
T
>
observable
<
T
>
observable
<
T
>::
observe_on
(
coordinator
*
other
,
size_t
buffer_size
,
observable
<
T
>
observable
<
T
>::
observe_on
(
coordinator
*
other
,
size_t
buffer_size
,
size_t
min_request_size
)
{
size_t
min_request_size
)
{
using
buffer_type
=
async
::
spsc_buffer
<
T
>
;
auto
[
pull
,
push
]
=
async
::
make_spsc_buffer_resource
<
T
>
(
buffer_size
,
auto
buf
=
make_counted
<
buffer_type
>
(
buffer_size
,
min_request_size
);
min_request_size
);
subscribe
(
async
::
producer_resource
<
T
>
{
buf
});
subscribe
(
push
);
auto
adapter
=
make_counted
<
observable_buffer_impl
<
buffer_type
>>
(
other
,
buf
);
return
make_observable
<
op
::
from_resource
<
T
>>
(
other
,
std
::
move
(
pull
));
buf
->
set_consumer
(
adapter
);
other
->
watch
(
adapter
->
as_disposable
());
return
observable
<
T
>
{
std
::
move
(
adapter
)};
}
}
// -- observable::subscribe ----------------------------------------------------
// -- observable::subscribe ----------------------------------------------------
...
@@ -2018,289 +497,13 @@ disposable observable<T>::subscribe(async::producer_resource<T> resource) {
...
@@ -2018,289 +497,13 @@ disposable observable<T>::subscribe(async::producer_resource<T> resource) {
auto
adapter
=
make_counted
<
adapter_type
>
(
pimpl_
->
ctx
(),
buf
);
auto
adapter
=
make_counted
<
adapter_type
>
(
pimpl_
->
ctx
(),
buf
);
buf
->
set_producer
(
adapter
);
buf
->
set_producer
(
adapter
);
auto
obs
=
adapter
->
as_observer
();
auto
obs
=
adapter
->
as_observer
();
pimpl_
->
ctx
()
->
watch
(
obs
.
as_disposable
());
auto
sub
=
subscribe
(
std
::
move
(
obs
));
return
subscribe
(
std
::
move
(
obs
));
pimpl_
->
ctx
()
->
watch
(
sub
);
return
sub
;
}
else
{
}
else
{
CAF_LOG_DEBUG
(
"failed to open producer resource"
);
CAF_LOG_DEBUG
(
"failed to open producer resource"
);
return
{};
return
{};
}
}
}
}
// -- custom operators ---------------------------------------------------------
/// An observable that represents an empty range. As soon as an observer
/// requests values from this observable, it calls `on_complete`.
template
<
class
T
>
class
empty_observable_impl
:
public
observable_impl_base
<
T
>
{
public:
// -- member types -----------------------------------------------------------
using
super
=
observable_impl_base
<
T
>
;
using
output_type
=
T
;
// -- constructors, destructors, and assignment operators --------------------
explicit
empty_observable_impl
(
coordinator
*
ctx
)
:
super
(
ctx
)
{
// nop
}
// -- implementation of disposable_impl --------------------------------------
void
dispose
()
override
{
// nop
}
bool
disposed
()
const
noexcept
override
{
return
true
;
}
// -- implementation of observable<T>::impl ----------------------------------
void
on_request
(
disposable_impl
*
snk
,
size_t
)
override
{
static_cast
<
observer_impl
<
output_type
>*>
(
snk
)
->
on_complete
();
}
void
on_cancel
(
disposable_impl
*
)
override
{
// nop
}
disposable
subscribe
(
observer
<
output_type
>
sink
)
override
{
return
this
->
do_subscribe
(
sink
.
ptr
());
}
private:
coordinator
*
ctx_
;
};
/// An observable that never calls any callbacks on its subscribers.
template
<
class
T
>
class
mute_observable_impl
:
public
observable_impl_base
<
T
>
{
public:
// -- member types -----------------------------------------------------------
using
super
=
observable_impl_base
<
T
>
;
using
output_type
=
T
;
// -- constructors, destructors, and assignment operators --------------------
explicit
mute_observable_impl
(
coordinator
*
ctx
)
:
super
(
ctx
)
{
// nop
}
// -- implementation of disposable_impl --------------------------------------
void
dispose
()
override
{
if
(
!
disposed_
)
{
disposed_
=
true
;
for
(
auto
&
obs
:
observers_
)
obs
.
on_complete
();
}
}
bool
disposed
()
const
noexcept
override
{
return
disposed_
;
}
// -- implementation of observable<T>::impl ----------------------------------
void
on_request
(
disposable_impl
*
,
size_t
)
override
{
// nop
}
void
on_cancel
(
disposable_impl
*
)
override
{
// nop
}
disposable
subscribe
(
observer
<
output_type
>
sink
)
override
{
if
(
!
disposed_
)
{
auto
ptr
=
make_counted
<
subscription
::
nop_impl
>
();
sink
.
ptr
()
->
on_subscribe
(
subscription
{
std
::
move
(
ptr
)});
observers_
.
emplace_back
(
sink
);
return
sink
.
as_disposable
();
}
else
{
return
this
->
reject_subscription
(
sink
,
sec
::
disposed
);
}
}
private:
bool
disposed_
=
false
;
std
::
vector
<
observer
<
output_type
>>
observers_
;
};
/// An observable with minimal internal logic. Useful for writing unit tests.
template
<
class
T
>
class
passive_observable
:
public
observable_impl_base
<
T
>
{
public:
// -- member types -----------------------------------------------------------
using
output_type
=
T
;
using
super
=
observable_impl_base
<
T
>
;
// -- friends ----------------------------------------------------------------
CAF_INTRUSIVE_PTR_FRIENDS
(
passive_observable
)
// -- constructors, destructors, and assignment operators --------------------
passive_observable
(
coordinator
*
ctx
)
:
super
(
ctx
)
{
// nop
}
// -- implementation of disposable_impl --------------------------------------
void
dispose
()
override
{
if
(
is_active
(
state
))
{
demand
=
0
;
if
(
out
)
{
out
.
on_complete
();
out
=
nullptr
;
}
state
=
observable_state
::
completed
;
}
}
bool
disposed
()
const
noexcept
override
{
return
!
is_active
(
state
);
}
// -- implementation of observable_impl<T> -----------------------------------
void
on_request
(
disposable_impl
*
sink
,
size_t
n
)
override
{
if
(
out
.
ptr
()
==
sink
)
{
demand
+=
n
;
}
}
void
on_cancel
(
disposable_impl
*
sink
)
override
{
if
(
out
.
ptr
()
==
sink
)
{
demand
=
0
;
out
=
nullptr
;
state
=
observable_state
::
completed
;
}
}
disposable
subscribe
(
observer
<
output_type
>
sink
)
override
{
if
(
state
==
observable_state
::
idle
)
{
state
=
observable_state
::
running
;
out
=
sink
;
return
this
->
do_subscribe
(
sink
.
ptr
());
}
else
if
(
is_final
(
state
))
{
return
this
->
reject_subscription
(
sink
,
sec
::
disposed
);
}
else
{
return
this
->
reject_subscription
(
sink
,
sec
::
too_many_observers
);
}
}
// -- pushing items ----------------------------------------------------------
void
push
(
span
<
const
output_type
>
items
)
{
if
(
items
.
size
()
>
demand
)
CAF_RAISE_ERROR
(
"observables must not emit more items than requested"
);
demand
-=
items
.
size
();
for
(
auto
&
item
:
items
)
out
.
on_next
(
item
);
}
void
push
(
const
output_type
&
item
)
{
if
(
demand
==
0
)
CAF_RAISE_ERROR
(
"observables must not emit more items than requested"
);
--
demand
;
out
.
on_next
(
item
);
}
template
<
class
...
Ts
>
void
push
(
output_type
x0
,
output_type
x1
,
Ts
...
xs
)
{
output_type
items
[]
=
{
std
::
move
(
x0
),
std
::
move
(
x1
),
std
::
move
(
xs
)...};
push
(
make_span
(
items
,
sizeof
...(
Ts
)
+
2
));
}
void
complete
()
{
if
(
is_active
(
state
))
{
demand
=
0
;
if
(
out
)
{
out
.
on_complete
();
out
=
nullptr
;
}
state
=
observable_state
::
completed
;
}
}
void
abort
(
const
error
&
reason
)
{
if
(
is_active
(
state
))
{
demand
=
0
;
if
(
out
)
{
out
.
on_error
(
reason
);
out
=
nullptr
;
}
state
=
observable_state
::
aborted
;
}
}
// -- member variables -------------------------------------------------------
observable_state
state
=
observable_state
::
idle
;
size_t
demand
=
0
;
observer
<
T
>
out
;
};
/// @relates passive_observable
template
<
class
T
>
intrusive_ptr
<
passive_observable
<
T
>>
make_passive_observable
(
coordinator
*
ctx
)
{
return
make_counted
<
passive_observable
<
T
>>
(
ctx
);
}
/// Implementation of the `defer` operator.
template
<
class
T
,
class
Factory
>
class
defer_observable_impl
:
public
ref_counted
,
public
observable_impl
<
T
>
{
public:
// -- constructors, destructors, and assignment operators --------------------
defer_observable_impl
(
coordinator
*
ctx
,
Factory
factory
)
:
ctx_
(
ctx
),
factory_
(
std
::
move
(
factory
))
{
// nop
}
// -- implementation of disposable_impl --------------------------------------
void
ref_disposable
()
const
noexcept
final
{
this
->
ref
();
}
void
deref_disposable
()
const
noexcept
final
{
this
->
deref
();
}
void
dispose
()
override
{
factory_
=
std
::
nullopt
;
}
bool
disposed
()
const
noexcept
override
{
return
factory_
.
has_value
();
}
// -- implementation of observable<T>::impl ----------------------------------
coordinator
*
ctx
()
const
noexcept
final
{
return
ctx_
;
}
disposable
subscribe
(
observer
<
T
>
what
)
override
{
if
(
factory_
)
{
return
(
*
factory_
)().
subscribe
(
what
);
}
else
{
what
.
on_error
(
make_error
(
sec
::
invalid_observable
));
return
disposable
{};
}
}
private:
coordinator
*
ctx_
;
std
::
optional
<
Factory
>
factory_
;
};
}
// namespace caf::flow
}
// namespace caf::flow
libcaf_core/caf/flow/observable_builder.hpp
View file @
04e02412
...
@@ -7,10 +7,20 @@
...
@@ -7,10 +7,20 @@
#include "caf/async/spsc_buffer.hpp"
#include "caf/async/spsc_buffer.hpp"
#include "caf/defaults.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/detail/ref_counted_base.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/observable.hpp"
#include "caf/flow/observable.hpp"
#include "caf/flow/op/defer.hpp"
#include "caf/flow/op/empty.hpp"
#include "caf/flow/op/fail.hpp"
#include "caf/flow/op/from_generator.hpp"
#include "caf/flow/op/from_resource.hpp"
#include "caf/flow/op/interval.hpp"
#include "caf/flow/op/never.hpp"
#include "caf/flow/op/zip_with.hpp"
#include <cstdint>
#include <cstdint>
#include <functional>
namespace
caf
::
flow
{
namespace
caf
::
flow
{
...
@@ -30,60 +40,10 @@ class callable_source;
...
@@ -30,60 +40,10 @@ class callable_source;
// -- special-purpose observable implementations -------------------------------
// -- special-purpose observable implementations -------------------------------
class
interval_action
;
class
CAF_CORE_EXPORT
interval_impl
:
public
observable_impl_base
<
int64_t
>
{
public:
// -- member types -----------------------------------------------------------
using
output_type
=
int64_t
;
using
super
=
observable_impl_base
<
int64_t
>
;
// -- friends ----------------------------------------------------------------
CAF_INTRUSIVE_PTR_FRIENDS
(
interval_impl
)
friend
class
interval_action
;
// -- constructors, destructors, and assignment operators --------------------
interval_impl
(
coordinator
*
ctx
,
timespan
initial_delay
,
timespan
period
);
interval_impl
(
coordinator
*
ctx
,
timespan
initial_delay
,
timespan
period
,
int64_t
max_val
);
// -- implementation of disposable::impl -------------------------------------
void
dispose
()
override
;
bool
disposed
()
const
noexcept
override
;
// -- implementation of observable<T>::impl ----------------------------------
void
on_request
(
disposable_impl
*
,
size_t
)
override
;
void
on_cancel
(
disposable_impl
*
)
override
;
disposable
subscribe
(
observer
<
int64_t
>
what
)
override
;
private:
void
fire
(
interval_action
*
);
observer
<
int64_t
>
obs_
;
disposable
pending_
;
timespan
initial_delay_
;
timespan
period_
;
coordinator
::
steady_time_point
last_
;
int64_t
val_
=
0
;
int64_t
max_
;
size_t
demand_
=
0
;
};
// -- builder interface --------------------------------------------------------
// -- builder interface --------------------------------------------------------
/// Factory for @ref observable objects.
/// Factory for @ref observable objects.
class
observable_builder
{
class
CAF_CORE_EXPORT
observable_builder
{
public:
public:
friend
class
coordinator
;
friend
class
coordinator
;
...
@@ -114,6 +74,13 @@ public:
...
@@ -114,6 +74,13 @@ public:
return
from_callable
([
x
=
std
::
move
(
init
)]()
mutable
{
return
x
++
;
});
return
from_callable
([
x
=
std
::
move
(
init
)]()
mutable
{
return
x
++
;
});
}
}
/// Creates a @ref generation that emits `num` ascending values, starting with
/// `init`.
template
<
class
T
>
[[
nodiscard
]]
auto
range
(
T
init
,
size_t
num
)
const
{
return
iota
(
init
).
take
(
num
);
}
/// Creates a @ref generation that emits values by repeatedly calling
/// Creates a @ref generation that emits values by repeatedly calling
/// `pullable.pull(...)`. For example implementations of the `Pullable`
/// `pullable.pull(...)`. For example implementations of the `Pullable`
/// concept, see @ref container_source, @ref repeater_source and
/// concept, see @ref container_source, @ref repeater_source and
...
@@ -134,11 +101,7 @@ public:
...
@@ -134,11 +101,7 @@ public:
[[
nodiscard
]]
observable
<
int64_t
>
[[
nodiscard
]]
observable
<
int64_t
>
interval
(
std
::
chrono
::
duration
<
Rep
,
Period
>
initial_delay
,
interval
(
std
::
chrono
::
duration
<
Rep
,
Period
>
initial_delay
,
std
::
chrono
::
duration
<
Rep
,
Period
>
period
)
{
std
::
chrono
::
duration
<
Rep
,
Period
>
period
)
{
// Intervals introduce a time-dependency, so we need to watch them in order
return
make_observable
<
op
::
interval
>
(
ctx_
,
initial_delay
,
period
);
// to prevent actors from shutting down while timeouts are still pending.
auto
ptr
=
make_counted
<
interval_impl
>
(
ctx_
,
initial_delay
,
period
);
ctx_
->
watch
(
ptr
->
as_disposable
());
return
observable
<
int64_t
>
{
std
::
move
(
ptr
)};
}
}
/// Creates an @ref observable that emits a sequence of integers spaced by the
/// Creates an @ref observable that emits a sequence of integers spaced by the
...
@@ -154,39 +117,98 @@ public:
...
@@ -154,39 +117,98 @@ public:
template
<
class
Rep
,
class
Period
>
template
<
class
Rep
,
class
Period
>
[[
nodiscard
]]
observable
<
int64_t
>
[[
nodiscard
]]
observable
<
int64_t
>
timer
(
std
::
chrono
::
duration
<
Rep
,
Period
>
delay
)
{
timer
(
std
::
chrono
::
duration
<
Rep
,
Period
>
delay
)
{
auto
ptr
=
make_counted
<
interval_impl
>
(
ctx_
,
delay
,
delay
,
1
);
return
make_observable
<
op
::
interval
>
(
ctx_
,
delay
,
delay
,
1
);
ctx_
->
watch
(
ptr
->
as_disposable
());
return
observable
<
int64_t
>
{
std
::
move
(
ptr
)};
}
}
/// Creates an @ref observable without any values that calls `on_complete`
/// Creates an @ref observable without any values that calls `on_complete`
/// after subscribing to it.
/// after subscribing to it.
template
<
class
T
>
template
<
class
T
>
[[
nodiscard
]]
observable
<
T
>
empty
()
{
[[
nodiscard
]]
observable
<
T
>
empty
()
{
return
make_observable
<
empty_observable_impl
<
T
>>
(
ctx_
);
return
make_observable
<
op
::
empty
<
T
>>
(
ctx_
);
}
}
/// Creates an @ref observable without any values that also never terminates.
/// Creates an @ref observable without any values that also never terminates.
template
<
class
T
>
template
<
class
T
>
[[
nodiscard
]]
observable
<
T
>
never
()
{
[[
nodiscard
]]
observable
<
T
>
never
()
{
return
make_observable
<
mute_observable_impl
<
T
>>
(
ctx_
);
return
make_observable
<
op
::
never
<
T
>>
(
ctx_
);
}
}
/// Creates an @ref observable without any values that
calls `on_error` after
/// Creates an @ref observable without any values that
fails immediately when
/// subscribing to it.
/// subscribing to it
by calling `on_error` on the subscriber
.
template
<
class
T
>
template
<
class
T
>
[[
nodiscard
]]
observable
<
T
>
error
(
caf
::
error
what
)
{
[[
nodiscard
]]
observable
<
T
>
fail
(
error
what
)
{
return
make_observable
<
o
bservable_error_imp
l
<
T
>>
(
ctx_
,
std
::
move
(
what
));
return
make_observable
<
o
p
::
fai
l
<
T
>>
(
ctx_
,
std
::
move
(
what
));
}
}
/// Create a fresh @ref observable for each @ref observer using the factory.
/// Create a fresh @ref observable for each @ref observer using the factory.
template
<
class
Factory
>
template
<
class
Factory
>
[[
nodiscard
]]
auto
defer
(
Factory
factory
)
{
[[
nodiscard
]]
auto
defer
(
Factory
factory
)
{
using
res_t
=
decltype
(
factory
());
return
make_observable
<
op
::
defer
<
Factory
>>
(
ctx_
,
std
::
move
(
factory
));
static_assert
(
is_observable_v
<
res_t
>
);
}
using
out_t
=
typename
res_t
::
output_type
;
using
impl_t
=
defer_observable_impl
<
out_t
,
Factory
>
;
/// Creates an @ref observable that combines the items emitted from all passed
return
make_observable
<
impl_t
>
(
ctx_
,
std
::
move
(
factory
));
/// source observables.
template
<
class
Input
,
class
...
Inputs
>
auto
merge
(
Input
&&
x
,
Inputs
&&
...
xs
)
{
using
in_t
=
std
::
decay_t
<
Input
>
;
if
constexpr
(
is_observable_v
<
in_t
>
)
{
using
impl_t
=
op
::
merge
<
output_type_t
<
in_t
>>
;
return
make_observable
<
impl_t
>
(
ctx_
,
std
::
forward
<
Input
>
(
x
),
std
::
forward
<
Inputs
>
(
xs
)...);
}
else
{
static_assert
(
detail
::
is_iterable_v
<
in_t
>
);
using
val_t
=
typename
in_t
::
value_type
;
static_assert
(
is_observable_v
<
val_t
>
);
using
impl_t
=
op
::
merge
<
output_type_t
<
val_t
>>
;
return
make_observable
<
impl_t
>
(
ctx_
,
std
::
forward
<
Input
>
(
x
),
std
::
forward
<
Inputs
>
(
xs
)...);
}
}
/// Creates an @ref observable that concatenates the items emitted from all
/// passed source observables.
template
<
class
Input
,
class
...
Inputs
>
auto
concat
(
Input
&&
x
,
Inputs
&&
...
xs
)
{
using
in_t
=
std
::
decay_t
<
Input
>
;
if
constexpr
(
is_observable_v
<
in_t
>
)
{
using
impl_t
=
op
::
concat
<
output_type_t
<
in_t
>>
;
return
make_observable
<
impl_t
>
(
ctx_
,
std
::
forward
<
Input
>
(
x
),
std
::
forward
<
Inputs
>
(
xs
)...);
}
else
{
static_assert
(
detail
::
is_iterable_v
<
in_t
>
);
using
val_t
=
typename
in_t
::
value_type
;
static_assert
(
is_observable_v
<
val_t
>
);
using
impl_t
=
op
::
concat
<
output_type_t
<
val_t
>>
;
return
make_observable
<
impl_t
>
(
ctx_
,
std
::
forward
<
Input
>
(
x
),
std
::
forward
<
Inputs
>
(
xs
)...);
}
}
/// @param fn The zip function. Takes one element from each input at a time
/// and reduces them into a single result.
/// @param input0 The input at index 0.
/// @param input1 The input at index 1.
/// @param inputs The inputs for index > 1, if any.
template
<
class
F
,
class
T0
,
class
T1
,
class
...
Ts
>
auto
zip_with
(
F
fn
,
T0
input0
,
T1
input1
,
Ts
...
inputs
)
{
using
output_type
=
op
::
zip_with_output_t
<
F
,
//
typename
T0
::
output_type
,
typename
T1
::
output_type
,
typename
Ts
::
output_type
...
>
;
using
impl_t
=
op
::
zip_with
<
F
,
//
typename
T0
::
output_type
,
//
typename
T1
::
output_type
,
//
typename
Ts
::
output_type
...
>
;
if
(
input0
.
valid
()
&&
input1
.
valid
()
&&
(
inputs
.
valid
()
&&
...))
{
auto
ptr
=
make_counted
<
impl_t
>
(
ctx_
,
std
::
move
(
fn
),
std
::
move
(
input0
).
as_observable
(),
std
::
move
(
input1
).
as_observable
(),
std
::
move
(
inputs
).
as_observable
()...);
return
observable
<
output_type
>
{
std
::
move
(
ptr
)};
}
else
{
auto
ptr
=
make_counted
<
op
::
empty
<
output_type
>>
(
ctx_
);
return
observable
<
output_type
>
{
std
::
move
(
ptr
)};
}
}
}
private:
private:
...
@@ -209,12 +231,20 @@ public:
...
@@ -209,12 +231,20 @@ public:
pos_
=
values_
.
begin
();
pos_
=
values_
.
begin
();
}
}
container_source
()
=
default
;
container_source
(
container_source
&&
)
=
default
;
container_source
(
container_source
&&
)
=
default
;
container_source
&
operator
=
(
container_source
&&
)
=
default
;
container_source
&
operator
=
(
container_source
&&
)
=
default
;
container_source
()
=
delete
;
container_source
(
const
container_source
&
other
)
:
values_
(
other
.
values_
)
{
container_source
(
const
container_source
&
)
=
delete
;
pos_
=
values_
.
begin
();
container_source
&
operator
=
(
const
container_source
&
)
=
delete
;
std
::
advance
(
pos_
,
std
::
distance
(
other
.
values_
.
begin
(),
other
.
pos_
));
}
container_source
&
operator
=
(
const
container_source
&
other
)
{
values_
=
other
.
values_
;
pos_
=
values_
.
begin
();
std
::
advance
(
pos_
,
std
::
distance
(
other
.
values_
.
begin
(),
other
.
pos_
));
return
*
this
;
}
template
<
class
Step
,
class
...
Steps
>
template
<
class
Step
,
class
...
Steps
>
void
pull
(
size_t
n
,
Step
&
step
,
Steps
&
...
steps
)
{
void
pull
(
size_t
n
,
Step
&
step
,
Steps
&
...
steps
)
{
...
@@ -303,10 +333,9 @@ public:
...
@@ -303,10 +333,9 @@ public:
}
}
callable_source
(
callable_source
&&
)
=
default
;
callable_source
(
callable_source
&&
)
=
default
;
callable_source
(
const
callable_source
&
)
=
default
;
callable_source
&
operator
=
(
callable_source
&&
)
=
default
;
callable_source
&
operator
=
(
callable_source
&&
)
=
default
;
callable_source
&
operator
=
(
const
callable_source
&
)
=
default
;
callable_source
(
const
callable_source
&
)
=
delete
;
callable_source
&
operator
=
(
const
callable_source
&
)
=
delete
;
template
<
class
Step
,
class
...
Steps
>
template
<
class
Step
,
class
...
Steps
>
void
pull
(
size_t
n
,
Step
&
step
,
Steps
&
...
steps
)
{
void
pull
(
size_t
n
,
Step
&
step
,
Steps
&
...
steps
)
{
...
@@ -338,61 +367,6 @@ class generation final
...
@@ -338,61 +367,6 @@ class generation final
public:
public:
using
output_type
=
steps_output_type_t
<
Generator
,
Steps
...
>
;
using
output_type
=
steps_output_type_t
<
Generator
,
Steps
...
>
;
class
impl
:
public
observable_impl_base
<
output_type
>
{
public:
using
super
=
observable_impl_base
<
output_type
>
;
template
<
class
...
Ts
>
impl
(
coordinator
*
ctx
,
Generator
gen
,
Ts
&&
...
steps
)
:
super
(
ctx
),
gen_
(
std
::
move
(
gen
)),
steps_
(
std
::
forward
<
Ts
>
(
steps
)...)
{
// nop
}
// -- implementation of disposable::impl -----------------------------------
void
dispose
()
override
{
term_
.
dispose
();
}
bool
disposed
()
const
noexcept
override
{
return
!
term_
.
active
();
}
// -- implementation of observable_impl<T> ---------------------------------
disposable
subscribe
(
observer
<
output_type
>
sink
)
override
{
CAF_LOG_TRACE
(
CAF_ARG2
(
"sink"
,
sink
.
ptr
()));
auto
sub
=
term_
.
add
(
this
,
sink
);
if
(
sub
)
{
term_
.
start
();
}
return
sub
;
}
void
on_request
(
disposable_impl
*
sink
,
size_t
demand
)
override
{
CAF_LOG_TRACE
(
CAF_ARG
(
sink
)
<<
CAF_ARG
(
demand
));
if
(
auto
n
=
term_
.
on_request
(
sink
,
demand
);
n
>
0
)
{
auto
fn
=
[
this
,
n
](
auto
&
...
steps
)
{
gen_
.
pull
(
n
,
steps
...,
term_
);
};
std
::
apply
(
fn
,
steps_
);
term_
.
push
();
}
}
void
on_cancel
(
disposable_impl
*
sink
)
override
{
CAF_LOG_TRACE
(
CAF_ARG
(
sink
));
if
(
auto
n
=
term_
.
on_cancel
(
sink
);
n
>
0
)
{
auto
fn
=
[
this
,
n
](
auto
&
...
steps
)
{
gen_
.
pull
(
n
,
steps
...,
term_
);
};
std
::
apply
(
fn
,
steps_
);
term_
.
push
();
}
}
private:
Generator
gen_
;
std
::
tuple
<
Steps
...
>
steps_
;
broadcast_step
<
output_type
>
term_
;
};
template
<
class
...
Ts
>
template
<
class
...
Ts
>
generation
(
coordinator
*
ctx
,
Generator
gen
,
Ts
&&
...
steps
)
generation
(
coordinator
*
ctx
,
Generator
gen
,
Ts
&&
...
steps
)
:
ctx_
(
ctx
),
gen_
(
std
::
move
(
gen
)),
steps_
(
std
::
forward
<
Ts
>
(
steps
)...)
{
:
ctx_
(
ctx
),
gen_
(
std
::
move
(
gen
)),
steps_
(
std
::
forward
<
Ts
>
(
steps
)...)
{
...
@@ -426,20 +400,73 @@ public:
...
@@ -426,20 +400,73 @@ public:
filter_step
<
Predicate
>
{
std
::
move
(
predicate
)});
filter_step
<
Predicate
>
{
std
::
move
(
predicate
)});
}
}
template
<
class
Predicate
>
auto
take_while
(
Predicate
predicate
)
&&
{
return
std
::
move
(
*
this
).
transform
(
take_while_step
<
Predicate
>
{
std
::
move
(
predicate
)});
}
template
<
class
Reducer
>
auto
reduce
(
output_type
init
,
Reducer
reducer
)
&&
{
return
std
::
move
(
*
this
).
transform
(
reduce_step
<
output_type
,
Reducer
>
{
init
,
reducer
});
}
auto
sum
()
&&
{
return
std
::
move
(
*
this
).
reduce
(
output_type
{},
std
::
plus
<
output_type
>
{});
}
auto
distinct
()
&&
{
return
std
::
move
(
*
this
).
transform
(
distinct_step
<
output_type
>
{});
}
template
<
class
Fn
>
template
<
class
Fn
>
auto
map
(
Fn
fn
)
&&
{
auto
map
(
Fn
fn
)
&&
{
return
std
::
move
(
*
this
).
transform
(
map_step
<
Fn
>
{
std
::
move
(
fn
)});
return
std
::
move
(
*
this
).
transform
(
map_step
<
Fn
>
{
std
::
move
(
fn
)});
}
}
template
<
class
...
Inputs
>
auto
merge
(
Inputs
&&
...
xs
)
&&
{
return
std
::
move
(
*
this
).
as_observable
().
merge
(
std
::
forward
<
Inputs
>
(
xs
)...);
}
template
<
class
...
Inputs
>
auto
concat
(
Inputs
&&
...
xs
)
&&
{
return
std
::
move
(
*
this
).
as_observable
().
concat
(
std
::
forward
<
Inputs
>
(
xs
)...);
}
template
<
class
F
>
template
<
class
F
>
auto
flat_map_optional
(
F
f
)
&&
{
auto
flat_map_optional
(
F
f
)
&&
{
return
std
::
move
(
*
this
).
transform
(
flat_map_optional_step
<
F
>
{
std
::
move
(
f
)});
return
std
::
move
(
*
this
).
transform
(
flat_map_optional_step
<
F
>
{
std
::
move
(
f
)});
}
}
template
<
class
F
>
auto
do_on_next
(
F
f
)
{
return
std
::
move
(
*
this
)
//
.
transform
(
do_on_next_step
<
output_type
,
F
>
{
std
::
move
(
f
)});
}
template
<
class
F
>
auto
do_on_complete
(
F
f
)
&&
{
return
std
::
move
(
*
this
).
transform
(
do_on_complete_step
<
output_type
,
F
>
{
std
::
move
(
f
)});
}
template
<
class
F
>
auto
do_on_error
(
F
f
)
&&
{
return
std
::
move
(
*
this
).
transform
(
do_on_error_step
<
output_type
,
F
>
{
std
::
move
(
f
)});
}
template
<
class
F
>
auto
do_finally
(
F
f
)
&&
{
return
std
::
move
(
*
this
).
transform
(
do_finally_step
<
output_type
,
F
>
{
std
::
move
(
f
)});
}
observable
<
output_type
>
as_observable
()
&&
override
{
observable
<
output_type
>
as_observable
()
&&
override
{
auto
pimpl
=
make_observable_impl
<
impl
>
(
ctx_
,
std
::
move
(
gen_
),
using
impl_t
=
op
::
from_generator
<
Generator
,
Steps
...
>
;
std
::
move
(
steps_
));
return
make_observable
<
impl_t
>
(
ctx_
,
std
::
move
(
gen_
),
std
::
move
(
steps_
));
return
observable
<
output_type
>
{
std
::
move
(
pimpl
)};
}
}
coordinator
*
ctx
()
const
noexcept
{
coordinator
*
ctx
()
const
noexcept
{
...
@@ -490,18 +517,8 @@ generation<callable_source<F>> observable_builder::from_callable(F fn) const {
...
@@ -490,18 +517,8 @@ generation<callable_source<F>> observable_builder::from_callable(F fn) const {
template
<
class
T
>
template
<
class
T
>
observable
<
T
>
observable
<
T
>
observable_builder
::
from_resource
(
async
::
consumer_resource
<
T
>
hdl
)
const
{
observable_builder
::
from_resource
(
async
::
consumer_resource
<
T
>
hdl
)
const
{
using
buffer_type
=
typename
async
::
consumer_resource
<
T
>::
buffer_type
;
using
impl_t
=
op
::
from_resource
<
T
>
;
using
res_t
=
observable
<
T
>
;
return
make_observable
<
impl_t
>
(
ctx_
,
std
::
move
(
hdl
));
if
(
auto
buf
=
hdl
.
try_open
())
{
auto
adapter
=
make_counted
<
observable_buffer_impl
<
buffer_type
>>
(
ctx_
,
buf
);
buf
->
set_consumer
(
adapter
);
ctx_
->
watch
(
adapter
->
as_disposable
());
return
res_t
{
std
::
move
(
adapter
)};
}
else
{
auto
err
=
make_error
(
sec
::
invalid_observable
,
"from_resource: failed to open the resource"
);
return
make_observable
<
observable_error_impl
<
T
>>
(
ctx_
,
std
::
move
(
err
));
}
}
}
// -- observable_builder::lift -------------------------------------------------
// -- observable_builder::lift -------------------------------------------------
...
...
libcaf_core/caf/flow/observable_decl.hpp
0 → 100644
View file @
04e02412
// 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/disposable.hpp"
#include "caf/flow/fwd.hpp"
#include "caf/flow/op/base.hpp"
#include "caf/flow/step.hpp"
#include "caf/fwd.hpp"
#include "caf/intrusive_ptr.hpp"
#include <cstddef>
#include <functional>
#include <utility>
#include <vector>
namespace
caf
::
flow
{
/// Represents a potentially unbound sequence of values.
template
<
class
T
>
class
observable
{
public:
/// The type of emitted items.
using
output_type
=
T
;
/// The pointer-to-implementation type.
using
pimpl_type
=
intrusive_ptr
<
op
::
base
<
T
>>
;
explicit
observable
(
pimpl_type
pimpl
)
noexcept
:
pimpl_
(
std
::
move
(
pimpl
))
{
// nop
}
observable
&
operator
=
(
std
::
nullptr_t
)
noexcept
{
pimpl_
.
reset
();
return
*
this
;
}
observable
()
noexcept
=
default
;
observable
(
observable
&&
)
noexcept
=
default
;
observable
(
const
observable
&
)
noexcept
=
default
;
observable
&
operator
=
(
observable
&&
)
noexcept
=
default
;
observable
&
operator
=
(
const
observable
&
)
noexcept
=
default
;
/// @copydoc impl::subscribe
disposable
subscribe
(
observer
<
T
>
what
)
{
if
(
pimpl_
)
{
return
pimpl_
->
subscribe
(
std
::
move
(
what
));
}
else
{
what
.
on_error
(
make_error
(
sec
::
invalid_observable
));
return
disposable
{};
}
}
/// Creates a new observer that pushes all observed items to the resource.
disposable
subscribe
(
async
::
producer_resource
<
T
>
resource
);
/// Returns a transformation that applies a step function to each input.
template
<
class
Step
>
transformation
<
Step
>
transform
(
Step
step
);
/// Registers a callback for `on_next` events.
template
<
class
F
>
auto
do_on_next
(
F
f
)
{
return
transform
(
do_on_next_step
<
T
,
F
>
{
std
::
move
(
f
)});
}
/// Registers a callback for `on_complete` events.
template
<
class
F
>
auto
do_on_complete
(
F
f
)
{
return
transform
(
do_on_complete_step
<
T
,
F
>
{
std
::
move
(
f
)});
}
/// Registers a callback for `on_error` events.
template
<
class
F
>
auto
do_on_error
(
F
f
)
{
return
transform
(
do_on_error_step
<
T
,
F
>
{
std
::
move
(
f
)});
}
/// Registers a callback that runs on `on_complete` or `on_error`.
template
<
class
F
>
auto
do_finally
(
F
f
)
{
return
transform
(
do_finally_step
<
T
,
F
>
{
std
::
move
(
f
)});
}
/// Catches errors by converting them into errors instead.
auto
on_error_complete
()
{
return
transform
(
on_error_complete_step
<
T
>
{});
}
/// Returns a transformation that selects only the first `n` items.
transformation
<
limit_step
<
T
>>
take
(
size_t
n
);
/// Returns a transformation that selects only items that satisfy `predicate`.
template
<
class
Predicate
>
transformation
<
filter_step
<
Predicate
>>
filter
(
Predicate
prediate
);
/// Returns a transformation that selects all value until the `predicate`
/// returns false.
template
<
class
Predicate
>
transformation
<
take_while_step
<
Predicate
>>
take_while
(
Predicate
prediate
);
/// Reduces the entire sequence of items to a single value. Other names for
/// the algorithm are `accumulate` and `fold`.
template
<
class
Reducer
>
transformation
<
reduce_step
<
T
,
Reducer
>>
reduce
(
T
init
,
Reducer
reducer
);
/// Accumulates all values and emits only the final result.
auto
sum
()
{
return
reduce
(
T
{},
std
::
plus
<
T
>
{});
}
/// Makes all values unique by suppressing all items that have been emitted in
/// the past.
transformation
<
distinct_step
<
T
>>
distinct
();
/// Returns a transformation that applies `f` to each input and emits the
/// result of the function application.
template
<
class
F
>
transformation
<
map_step
<
F
>>
map
(
F
f
);
/// Calls `on_next` for each item emitted by this observable.
template
<
class
OnNext
>
disposable
for_each
(
OnNext
on_next
);
/// Calls `on_next` for each item and `on_error` for each error emitted by
/// this observable.
template
<
class
OnNext
,
class
OnError
>
disposable
for_each
(
OnNext
on_next
,
OnError
on_error
);
/// Calls `on_next` for each item, `on_error` for each error and `on_complete`
/// for each completion signal emitted by this observable.
template
<
class
OnNext
,
class
OnError
,
class
OnComplete
>
disposable
for_each
(
OnNext
on_next
,
OnError
on_error
,
OnComplete
on_complete
);
/// Combines the output of multiple @ref observable objects into one by
/// merging their outputs. May also be called without arguments if the `T` is
/// an @ref observable.
template
<
class
...
Inputs
>
auto
merge
(
Inputs
&&
...
xs
);
/// Combines the output of multiple @ref observable objects into one by
/// concatenating their outputs. May also be called without arguments if the
/// `T` is an @ref observable.
template
<
class
...
Inputs
>
auto
concat
(
Inputs
&&
...);
/// Returns a transformation that emits items by merging the outputs of all
/// observables returned by `f`.
template
<
class
F
>
auto
flat_map
(
F
f
);
/// Returns a transformation that emits items from optional values returned by
/// `f`.
template
<
class
F
>
transformation
<
flat_map_optional_step
<
F
>>
flat_map_optional
(
F
f
);
/// Returns a transformation that emits items by concatenating the outputs of
/// all observables returned by `f`.
template
<
class
F
>
auto
concat_map
(
F
f
);
/// Takes @p prefix_size elements from this observable and emits it in a tuple
/// containing an observable for the remaining elements as the second value.
/// The returned observable either emits a single element (the tuple) or none
/// if this observable never produces sufficient elements for the prefix.
/// @pre `prefix_size > 0`
observable
<
cow_tuple
<
std
::
vector
<
T
>
,
observable
<
T
>>>
prefix_and_tail
(
size_t
prefix_size
);
/// Similar to `prefix_and_tail(1)` but passes the single element directly in
/// the tuple instead of wrapping it in a list.
observable
<
cow_tuple
<
T
,
observable
<
T
>>>
head_and_tail
();
/// Creates an asynchronous resource that makes emitted items available in a
/// spsc buffer.
async
::
consumer_resource
<
T
>
to_resource
(
size_t
buffer_size
,
size_t
min_request_size
);
async
::
consumer_resource
<
T
>
to_resource
()
{
return
to_resource
(
defaults
::
flow
::
buffer_size
,
defaults
::
flow
::
min_demand
);
}
observable
observe_on
(
coordinator
*
other
,
size_t
buffer_size
,
size_t
min_request_size
);
observable
observe_on
(
coordinator
*
other
)
{
return
observe_on
(
other
,
defaults
::
flow
::
buffer_size
,
defaults
::
flow
::
min_demand
);
}
const
observable
&
as_observable
()
const
&
noexcept
{
return
std
::
move
(
*
this
);
}
observable
&&
as_observable
()
&&
noexcept
{
return
std
::
move
(
*
this
);
}
const
pimpl_type
&
pimpl
()
const
noexcept
{
return
pimpl_
;
}
bool
valid
()
const
noexcept
{
return
pimpl_
!=
nullptr
;
}
explicit
operator
bool
()
const
noexcept
{
return
valid
();
}
bool
operator
!
()
const
noexcept
{
return
!
valid
();
}
void
swap
(
observable
&
other
)
{
pimpl_
.
swap
(
other
.
pimpl_
);
}
/// @pre `valid()`
coordinator
*
ctx
()
const
{
return
pimpl_
->
ctx
();
}
private:
pimpl_type
pimpl_
;
};
/// Convenience function for creating an @ref observable from a concrete
/// operator type.
/// @relates observable
template
<
class
Operator
,
class
...
Ts
>
observable
<
typename
Operator
::
output_type
>
make_observable
(
coordinator
*
ctx
,
Ts
&&
...
xs
)
{
using
out_t
=
typename
Operator
::
output_type
;
using
ptr_t
=
intrusive_ptr
<
op
::
base
<
out_t
>>
;
ptr_t
ptr
{
new
Operator
(
ctx
,
std
::
forward
<
Ts
>
(
xs
)...),
false
};
return
observable
<
out_t
>
{
std
::
move
(
ptr
)};
}
// Note: the definition of all member functions is in observable.hpp.
}
// namespace caf::flow
libcaf_core/caf/flow/observer.hpp
View file @
04e02412
...
@@ -7,9 +7,11 @@
...
@@ -7,9 +7,11 @@
#include "caf/async/batch.hpp"
#include "caf/async/batch.hpp"
#include "caf/async/producer.hpp"
#include "caf/async/producer.hpp"
#include "caf/defaults.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/comparable.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/disposable.hpp"
#include "caf/disposable.hpp"
#include "caf/error.hpp"
#include "caf/error.hpp"
#include "caf/flow/coordinated.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/observer_state.hpp"
#include "caf/flow/observer_state.hpp"
#include "caf/flow/subscription.hpp"
#include "caf/flow/subscription.hpp"
...
@@ -23,10 +25,10 @@ namespace caf::flow {
...
@@ -23,10 +25,10 @@ namespace caf::flow {
/// Handle to a consumer of items.
/// Handle to a consumer of items.
template
<
class
T
>
template
<
class
T
>
class
observer
{
class
observer
:
detail
::
comparable
<
observer
<
T
>>
{
public:
public:
/// Internal interface of an `observer`.
/// Internal interface of an `observer`.
class
impl
:
public
disposable
::
impl
{
class
impl
:
public
coordinated
{
public:
public:
using
input_type
=
T
;
using
input_type
=
T
;
...
@@ -61,14 +63,6 @@ public:
...
@@ -61,14 +63,6 @@ public:
observer
&
operator
=
(
observer
&&
)
noexcept
=
default
;
observer
&
operator
=
(
observer
&&
)
noexcept
=
default
;
observer
&
operator
=
(
const
observer
&
)
noexcept
=
default
;
observer
&
operator
=
(
const
observer
&
)
noexcept
=
default
;
disposable
as_disposable
()
const
&
noexcept
{
return
disposable
{
pimpl_
};
}
disposable
as_disposable
()
&&
noexcept
{
return
disposable
{
std
::
move
(
pimpl_
)};
}
/// @pre `valid()`
/// @pre `valid()`
void
on_complete
()
{
void
on_complete
()
{
pimpl_
->
on_complete
();
pimpl_
->
on_complete
();
...
@@ -129,10 +123,14 @@ public:
...
@@ -129,10 +123,14 @@ public:
return
std
::
move
(
pimpl_
);
return
std
::
move
(
pimpl_
);
}
}
void
swap
(
observer
&
other
)
{
void
swap
(
observer
&
other
)
noexcept
{
pimpl_
.
swap
(
other
.
pimpl_
);
pimpl_
.
swap
(
other
.
pimpl_
);
}
}
intptr_t
compare
(
const
observer
&
other
)
const
noexcept
{
return
pimpl_
.
compare
(
other
.
pimpl_
);
}
private:
private:
intrusive_ptr
<
impl
>
pimpl_
;
intrusive_ptr
<
impl
>
pimpl_
;
};
};
...
@@ -166,7 +164,7 @@ using on_next_value_type = typename on_next_trait_t<F>::value_type;
...
@@ -166,7 +164,7 @@ using on_next_value_type = typename on_next_trait_t<F>::value_type;
template
<
class
OnNext
,
class
OnError
=
unit_t
,
class
OnComplete
=
unit_t
>
template
<
class
OnNext
,
class
OnError
=
unit_t
,
class
OnComplete
=
unit_t
>
class
default_observer_impl
class
default_observer_impl
:
public
ref_counted
,
:
public
detail
::
ref_counted_base
,
public
flow
::
observer_impl
<
on_next_value_type
<
OnNext
>>
{
public
flow
::
observer_impl
<
on_next_value_type
<
OnNext
>>
{
public:
public:
static_assert
(
std
::
is_invocable_v
<
OnError
,
const
error
&>
);
static_assert
(
std
::
is_invocable_v
<
OnError
,
const
error
&>
);
...
@@ -175,8 +173,6 @@ public:
...
@@ -175,8 +173,6 @@ public:
using
input_type
=
on_next_value_type
<
OnNext
>
;
using
input_type
=
on_next_value_type
<
OnNext
>
;
CAF_INTRUSIVE_PTR_FRIENDS
(
default_observer_impl
)
explicit
default_observer_impl
(
OnNext
&&
on_next_fn
)
explicit
default_observer_impl
(
OnNext
&&
on_next_fn
)
:
on_next_
(
std
::
move
(
on_next_fn
))
{
:
on_next_
(
std
::
move
(
on_next_fn
))
{
// nop
// nop
...
@@ -195,39 +191,23 @@ public:
...
@@ -195,39 +191,23 @@ public:
// nop
// nop
}
}
void
ref_disposable
()
const
noexcept
final
{
this
->
ref
();
}
void
deref_disposable
()
const
noexcept
final
{
this
->
deref
();
}
void
on_next
(
const
input_type
&
item
)
override
{
void
on_next
(
const
input_type
&
item
)
override
{
if
(
!
completed_
)
{
on_next_
(
item
);
on_next_
(
item
);
sub_
.
request
(
1
);
sub_
.
request
(
1
);
}
}
}
void
on_error
(
const
error
&
what
)
override
{
void
on_error
(
const
error
&
what
)
override
{
if
(
!
completed_
)
{
on_error_
(
what
);
on_error_
(
what
);
sub_
=
nullptr
;
sub_
=
nullptr
;
completed_
=
true
;
}
}
}
void
on_complete
()
override
{
void
on_complete
()
override
{
if
(
!
completed_
)
{
on_complete_
();
on_complete_
();
sub_
=
nullptr
;
sub_
=
nullptr
;
completed_
=
true
;
}
}
}
void
on_subscribe
(
flow
::
subscription
sub
)
override
{
void
on_subscribe
(
flow
::
subscription
sub
)
override
{
if
(
!
completed_
&&
!
sub_
)
{
if
(
!
sub_
)
{
sub_
=
std
::
move
(
sub
);
sub_
=
std
::
move
(
sub
);
sub_
.
request
(
defaults
::
flow
::
buffer_size
);
sub_
.
request
(
defaults
::
flow
::
buffer_size
);
}
else
{
}
else
{
...
@@ -235,23 +215,15 @@ public:
...
@@ -235,23 +215,15 @@ public:
}
}
}
}
void
dispose
()
override
{
void
ref_coordinated
()
const
noexcept
override
{
if
(
!
completed_
)
{
this
->
ref
();
on_complete_
();
if
(
sub_
)
{
sub_
.
cancel
();
sub_
=
nullptr
;
}
completed_
=
true
;
}
}
}
bool
dispos
ed
()
const
noexcept
override
{
void
deref_coordinat
ed
()
const
noexcept
override
{
return
completed_
;
this
->
deref
()
;
}
}
private:
private:
bool
completed_
=
false
;
OnNext
on_next_
;
OnNext
on_next_
;
OnError
on_error_
;
OnError
on_error_
;
OnComplete
on_complete_
;
OnComplete
on_complete_
;
...
@@ -313,7 +285,7 @@ auto make_observer_from_ptr(SmartPointer ptr) {
...
@@ -313,7 +285,7 @@ auto make_observer_from_ptr(SmartPointer ptr) {
/// Writes observed values to a bounded buffer.
/// Writes observed values to a bounded buffer.
template
<
class
Buffer
>
template
<
class
Buffer
>
class
buffer_writer_impl
:
public
ref_counted
,
class
buffer_writer_impl
:
public
detail
::
ref_counted_base
,
public
observer_impl
<
typename
Buffer
::
value_type
>
,
public
observer_impl
<
typename
Buffer
::
value_type
>
,
public
async
::
producer
{
public
async
::
producer
{
public:
public:
...
@@ -340,22 +312,13 @@ public:
...
@@ -340,22 +312,13 @@ public:
buf_
->
close
();
buf_
->
close
();
}
}
// -- implementation of
disposable::impl
-------------------------------------
// -- implementation of
coordinated -----
-------------------------------------
void
dispose
()
override
{
void
ref_coordinated
()
const
noexcept
final
{
CAF_LOG_TRACE
(
""
);
on_complete
();
}
bool
disposed
()
const
noexcept
override
{
return
buf_
==
nullptr
;
}
void
ref_disposable
()
const
noexcept
final
{
this
->
ref
();
this
->
ref
();
}
}
void
deref_
disposable
()
const
noexcept
final
{
void
deref_
coordinated
()
const
noexcept
final
{
this
->
deref
();
this
->
deref
();
}
}
...
@@ -455,123 +418,178 @@ private:
...
@@ -455,123 +418,178 @@ private:
/// Forwards all events to a parent.
/// Forwards all events to a parent.
template
<
class
T
,
class
Parent
,
class
Token
>
template
<
class
T
,
class
Parent
,
class
Token
>
class
forwarder
:
public
ref_counted
,
public
observer_impl
<
T
>
{
class
forwarder
:
public
detail
::
ref_counted_base
,
public
observer_impl
<
T
>
{
public:
public:
CAF_INTRUSIVE_PTR_FRIENDS
(
forwarder
)
// -- constructors, destructors, and assignment operators --------------------
explicit
forwarder
(
intrusive_ptr
<
Parent
>
parent
,
Token
token
)
explicit
forwarder
(
intrusive_ptr
<
Parent
>
parent
,
Token
token
)
:
parent
(
std
::
move
(
parent
)),
token
(
std
::
move
(
token
))
{
:
parent
_
(
std
::
move
(
parent
)),
token_
(
std
::
move
(
token
))
{
// nop
// nop
}
}
// -- implementation of coordinated ------------------------------------------
void
ref_coordinated
()
const
noexcept
final
{
this
->
ref
();
}
void
deref_coordinated
()
const
noexcept
final
{
this
->
deref
();
}
// -- implementation of observer_impl<T> -------------------------------------
void
on_complete
()
override
{
void
on_complete
()
override
{
if
(
parent
)
{
if
(
parent
_
)
{
parent
->
fwd_on_complete
(
token
);
parent
_
->
fwd_on_complete
(
token_
);
parent
=
nullptr
;
parent
_
=
nullptr
;
}
}
}
}
void
on_error
(
const
error
&
what
)
override
{
void
on_error
(
const
error
&
what
)
override
{
if
(
parent
)
{
if
(
parent
_
)
{
parent
->
fwd_on_error
(
token
,
what
);
parent
_
->
fwd_on_error
(
token_
,
what
);
parent
=
nullptr
;
parent
_
=
nullptr
;
}
}
}
}
void
on_subscribe
(
subscription
new_sub
)
override
{
void
on_subscribe
(
subscription
new_sub
)
override
{
if
(
parent
)
{
if
(
parent
_
)
{
parent
->
fwd_on_subscribe
(
token
,
std
::
move
(
new_sub
));
parent
_
->
fwd_on_subscribe
(
token_
,
std
::
move
(
new_sub
));
}
else
{
}
else
{
new_sub
.
cancel
();
new_sub
.
cancel
();
}
}
}
}
void
on_next
(
const
T
&
item
)
override
{
void
on_next
(
const
T
&
item
)
override
{
if
(
parent
)
{
if
(
parent
_
)
{
parent
->
fwd_on_next
(
token
,
item
);
parent
_
->
fwd_on_next
(
token_
,
item
);
}
}
}
}
void
dispose
()
override
{
private:
on_complete
();
intrusive_ptr
<
Parent
>
parent_
;
}
Token
token_
;
};
bool
disposed
()
const
noexcept
override
{
/// An observer with minimal internal logic. Useful for writing unit tests.
return
!
parent
;
template
<
class
T
>
}
class
passive_observer
:
public
detail
::
ref_counted_base
,
public
observer_impl
<
T
>
{
public:
// -- implementation of coordinated ------------------------------------------
void
ref_
disposable
()
const
noexcept
final
{
void
ref_
coordinated
()
const
noexcept
final
{
this
->
ref
();
this
->
ref
();
}
}
void
deref_
disposable
()
const
noexcept
final
{
void
deref_
coordinated
()
const
noexcept
final
{
this
->
deref
();
this
->
deref
();
}
}
intrusive_ptr
<
Parent
>
parent
;
// -- implementation of observer_impl<T> -------------------------------------
Token
token
;
};
/// An observer with minimal internal logic. Useful for writing unit tests.
template
<
class
T
>
class
passive_observer
:
public
ref_counted
,
public
observer_impl
<
T
>
{
public:
// -- friends ----------------------------------------------------------------
CAF_INTRUSIVE_PTR_FRIENDS
(
passive_observer
)
void
on_complete
()
override
{
if
(
sub
)
{
sub
.
cancel
();
sub
=
nullptr
;
}
state
=
observer_state
::
completed
;
}
// -- implementation of disposable::impl -------------------------------------
void
on_error
(
const
error
&
what
)
override
{
if
(
sub
)
{
sub
.
cancel
();
sub
=
nullptr
;
}
err
=
what
;
state
=
observer_state
::
aborted
;
}
void
dispose
(
)
override
{
void
on_subscribe
(
subscription
new_sub
)
override
{
if
(
!
disposed
()
)
{
if
(
state
==
observer_state
::
idle
)
{
if
(
sub
)
{
CAF_ASSERT
(
!
sub
);
sub
.
cancel
(
);
sub
=
std
::
move
(
new_sub
);
sub
=
nullptr
;
state
=
observer_state
::
subscribed
;
}
}
else
{
state
=
observer_state
::
disposed
;
new_sub
.
cancel
()
;
}
}
}
}
bool
disposed
()
const
noexcept
override
{
void
on_next
(
const
T
&
item
)
override
{
switch
(
state
)
{
buf
.
emplace_back
(
item
);
case
observer_state
:
:
completed
:
}
case
observer_state
:
:
aborted
:
case
observer_state
:
:
disposed
:
// -- convenience functions --------------------------------------------------
return
true
;
default:
bool
request
(
size_t
demand
)
{
return
false
;
if
(
sub
)
{
sub
.
request
(
demand
);
return
true
;
}
else
{
return
false
;
}
}
}
}
void
ref_disposable
()
const
noexcept
final
{
std
::
vector
<
T
>
sorted_buf
()
const
{
auto
result
=
buf
;
std
::
sort
(
result
.
begin
(),
result
.
end
());
return
result
;
}
// -- member variables -------------------------------------------------------
/// The subscription for requesting additional items.
subscription
sub
;
/// Default-constructed unless on_error was called.
error
err
;
/// Represents the current state of this observer.
observer_state
state
;
/// Stores all items received via `on_next`.
std
::
vector
<
T
>
buf
;
};
/// @relates passive_observer
template
<
class
T
>
intrusive_ptr
<
passive_observer
<
T
>>
make_passive_observer
()
{
return
make_counted
<
passive_observer
<
T
>>
();
}
/// Similar to @ref passive_observer but automatically requests items until
/// completed. Useful for writing unit tests.
template
<
class
T
>
class
auto_observer
:
public
detail
::
ref_counted_base
,
public
observer_impl
<
T
>
{
public:
// -- implementation of coordinated ------------------------------------------
void
ref_coordinated
()
const
noexcept
final
{
this
->
ref
();
this
->
ref
();
}
}
void
deref_
disposable
()
const
noexcept
final
{
void
deref_
coordinated
()
const
noexcept
final
{
this
->
deref
();
this
->
deref
();
}
}
// -- implementation of observer_impl<T> -------------------------------------
// -- implementation of observer_impl<T> -------------------------------------
void
on_complete
()
override
{
void
on_complete
()
override
{
if
(
!
disposed
())
{
if
(
sub
)
{
if
(
sub
)
{
sub
.
cancel
();
sub
.
cancel
();
sub
=
nullptr
;
sub
=
nullptr
;
}
state
=
observer_state
::
completed
;
}
}
state
=
observer_state
::
completed
;
}
}
void
on_error
(
const
error
&
what
)
override
{
void
on_error
(
const
error
&
what
)
override
{
if
(
!
disposed
())
{
if
(
sub
)
{
if
(
sub
)
{
sub
.
cancel
();
sub
.
cancel
();
sub
=
nullptr
;
sub
=
nullptr
;
}
err
=
what
;
state
=
observer_state
::
aborted
;
}
}
err
=
what
;
state
=
observer_state
::
aborted
;
}
}
void
on_subscribe
(
subscription
new_sub
)
override
{
void
on_subscribe
(
subscription
new_sub
)
override
{
...
@@ -579,6 +597,7 @@ public:
...
@@ -579,6 +597,7 @@ public:
CAF_ASSERT
(
!
sub
);
CAF_ASSERT
(
!
sub
);
sub
=
std
::
move
(
new_sub
);
sub
=
std
::
move
(
new_sub
);
state
=
observer_state
::
subscribed
;
state
=
observer_state
::
subscribed
;
sub
.
request
(
64
);
}
else
{
}
else
{
new_sub
.
cancel
();
new_sub
.
cancel
();
}
}
...
@@ -586,6 +605,15 @@ public:
...
@@ -586,6 +605,15 @@ public:
void
on_next
(
const
T
&
item
)
override
{
void
on_next
(
const
T
&
item
)
override
{
buf
.
emplace_back
(
item
);
buf
.
emplace_back
(
item
);
sub
.
request
(
1
);
}
// -- convenience functions --------------------------------------------------
std
::
vector
<
T
>
sorted_buf
()
const
{
auto
result
=
buf
;
std
::
sort
(
result
.
begin
(),
result
.
end
());
return
result
;
}
}
// -- member variables -------------------------------------------------------
// -- member variables -------------------------------------------------------
...
@@ -603,10 +631,10 @@ public:
...
@@ -603,10 +631,10 @@ public:
std
::
vector
<
T
>
buf
;
std
::
vector
<
T
>
buf
;
};
};
/// @relates
passive
_observer
/// @relates
auto
_observer
template
<
class
T
>
template
<
class
T
>
intrusive_ptr
<
passive_observer
<
T
>>
make_passive
_observer
()
{
intrusive_ptr
<
auto_observer
<
T
>>
make_auto
_observer
()
{
return
make_counted
<
passive
_observer
<
T
>>
();
return
make_counted
<
auto
_observer
<
T
>>
();
}
}
}
// namespace caf::flow
}
// namespace caf::flow
libcaf_core/caf/flow/op/README.md
0 → 100644
View file @
04e02412
The namespace
`caf::flow::op`
contains (private) implementation classes for
various operators.
libcaf_core/caf/flow/op/base.hpp
0 → 100644
View file @
04e02412
// 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/ref_counted_base.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/subscription.hpp"
namespace
caf
::
flow
::
op
{
/// Abstract base type for all flow operators.
template
<
class
T
>
class
base
:
public
coordinated
{
public:
/// The type of observed values.
using
output_type
=
T
;
/// Returns the @ref coordinator that executes this flow operator.
virtual
coordinator
*
ctx
()
const
noexcept
=
0
;
/// Subscribes a new observer to the operator.
virtual
disposable
subscribe
(
observer
<
T
>
what
)
=
0
;
};
}
// namespace caf::flow::op
libcaf_core/caf/flow/op/cell.hpp
0 → 100644
View file @
04e02412
// 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/flow/observer.hpp"
#include "caf/flow/op/hot.hpp"
#include "caf/flow/subscription.hpp"
#include "caf/none.hpp"
#include <memory>
#include <utility>
#include <variant>
#include <vector>
namespace
caf
::
flow
::
op
{
/// State shared between one multicast operator and one subscribed observer.
template
<
class
T
>
struct
cell_sub_state
{
std
::
variant
<
none_t
,
unit_t
,
T
,
error
>
content
;
std
::
vector
<
observer
<
T
>>
listeners
;
void
set_null
()
{
CAF_ASSERT
(
std
::
holds_alternative
<
none_t
>
(
content
));
content
=
unit
;
std
::
vector
<
observer
<
T
>>
xs
;
xs
.
swap
(
listeners
);
for
(
auto
&
listener
:
xs
)
{
listener
.
on_complete
();
}
}
void
set_value
(
T
item
)
{
CAF_ASSERT
(
std
::
holds_alternative
<
none_t
>
(
content
));
content
=
std
::
move
(
item
);
auto
&
ref
=
std
::
get
<
T
>
(
content
);
std
::
vector
<
observer
<
T
>>
xs
;
xs
.
swap
(
listeners
);
for
(
auto
&
listener
:
xs
)
{
listener
.
on_next
(
ref
);
listener
.
on_complete
();
}
}
void
set_error
(
error
what
)
{
CAF_ASSERT
(
std
::
holds_alternative
<
none_t
>
(
content
));
content
=
std
::
move
(
what
);
auto
&
ref
=
std
::
get
<
error
>
(
content
);
std
::
vector
<
observer
<
T
>>
xs
;
xs
.
swap
(
listeners
);
for
(
auto
&
listener
:
xs
)
listener
.
on_error
(
ref
);
}
void
listen
(
observer
<
T
>
listener
)
{
switch
(
content
.
index
())
{
case
1
:
listener
.
on_complete
();
break
;
case
2
:
listener
.
on_next
(
std
::
get
<
T
>
(
content
));
listener
.
on_complete
();
break
;
case
3
:
listener
.
on_error
(
std
::
get
<
error
>
(
content
));
break
;
default:
listeners
.
emplace_back
(
std
::
move
(
listener
));
}
}
void
drop
(
const
observer
<
T
>&
listener
)
{
if
(
auto
i
=
std
::
find
(
listeners
.
begin
(),
listeners
.
end
(),
listener
);
i
!=
listeners
.
end
())
listeners
.
erase
(
i
);
}
};
template
<
class
T
>
using
cell_sub_state_ptr
=
std
::
shared_ptr
<
cell_sub_state
<
T
>>
;
template
<
class
T
>
class
cell_sub
:
public
subscription
::
impl_base
{
public:
// -- constructors, destructors, and assignment operators --------------------
cell_sub
(
coordinator
*
ctx
,
cell_sub_state_ptr
<
T
>
state
,
observer
<
T
>
out
)
:
ctx_
(
ctx
),
state_
(
std
::
move
(
state
)),
out_
(
std
::
move
(
out
))
{
// nop
}
// -- implementation of subscription -----------------------------------------
bool
disposed
()
const
noexcept
override
{
return
!
state_
;
}
void
cancel
()
override
{
if
(
state_
)
{
state_
->
drop
(
out_
);
state_
=
nullptr
;
out_
=
nullptr
;
}
}
void
request
(
size_t
)
override
{
if
(
!
listening_
)
{
listening_
=
true
;
ctx_
->
delay_fn
([
state
=
state_
,
out
=
out_
]
{
//
state
->
listen
(
std
::
move
(
out
));
});
}
}
private:
coordinator
*
ctx_
;
bool
listening_
=
false
;
cell_sub_state_ptr
<
T
>
state_
;
observer
<
T
>
out_
;
};
// Base type for *hot* operators that multicast data to subscribed observers.
template
<
class
T
>
class
cell
:
public
hot
<
T
>
{
public:
// -- member types -----------------------------------------------------------
using
super
=
hot
<
T
>
;
using
state_type
=
cell_sub_state
<
T
>
;
using
state_ptr_type
=
cell_sub_state_ptr
<
T
>
;
using
observer_type
=
observer
<
T
>
;
// -- constructors, destructors, and assignment operators --------------------
explicit
cell
(
coordinator
*
ctx
)
:
super
(
ctx
),
state_
(
std
::
make_shared
<
state_type
>
())
{
// nop
}
void
set_null
()
{
state_
->
set_null
();
}
void
set_value
(
T
item
)
{
state_
->
set_value
(
std
::
move
(
item
));
}
void
set_error
(
error
what
)
{
state_
->
set_error
(
what
);
}
disposable
subscribe
(
observer
<
T
>
out
)
override
{
auto
ptr
=
make_counted
<
cell_sub
<
T
>>
(
super
::
ctx_
,
state_
,
out
);
out
.
on_subscribe
(
subscription
{
ptr
});
return
disposable
{
std
::
move
(
ptr
)};
}
protected:
cell_sub_state_ptr
<
T
>
state_
;
};
}
// namespace caf::flow::op
libcaf_core/caf/flow/op/cold.hpp
0 → 100644
View file @
04e02412
// 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/ref_counted_base.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/op/base.hpp"
#include "caf/flow/subscription.hpp"
namespace
caf
::
flow
::
op
{
/// Convenience base type for *cold* observable types.
template
<
class
T
>
class
cold
:
public
detail
::
ref_counted_base
,
public
base
<
T
>
{
public:
// -- member types -----------------------------------------------------------
using
output_type
=
T
;
// -- constructors, destructors, and assignment operators --------------------
explicit
cold
(
coordinator
*
ctx
)
:
ctx_
(
ctx
)
{
// nop
}
// -- implementation of disposable_impl --------------------------------------
void
ref_coordinated
()
const
noexcept
final
{
this
->
ref
();
}
void
deref_coordinated
()
const
noexcept
final
{
this
->
deref
();
}
// -- implementation of observable_impl<T> -----------------------------------
coordinator
*
ctx
()
const
noexcept
final
{
return
ctx_
;
}
protected:
// -- member variables -------------------------------------------------------
coordinator
*
ctx_
;
};
}
// namespace caf::flow::op
libcaf_core/caf/flow/op/concat.hpp
0 → 100644
View file @
04e02412
// 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/ref_counted_base.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/op/cold.hpp"
#include "caf/flow/op/empty.hpp"
#include "caf/flow/subscription.hpp"
#include <deque>
#include <memory>
#include <numeric>
#include <utility>
#include <variant>
#include <vector>
namespace
caf
::
flow
::
op
{
/// Combines items from any number of observables.
template
<
class
T
>
class
concat_sub
:
public
subscription
::
impl_base
{
public:
// -- member types -----------------------------------------------------------
using
input_key
=
size_t
;
using
input_type
=
std
::
variant
<
observable
<
T
>
,
observable
<
observable
<
T
>>>
;
// -- constructors, destructors, and assignment operators --------------------
concat_sub
(
coordinator
*
ctx
,
observer
<
T
>
out
,
std
::
vector
<
input_type
>
inputs
)
:
ctx_
(
ctx
),
out_
(
out
),
inputs_
(
std
::
move
(
inputs
))
{
CAF_ASSERT
(
!
inputs_
.
empty
());
subscribe_next
();
}
// -- input management -------------------------------------------------------
void
subscribe_to
(
observable
<
T
>
what
)
{
CAF_ASSERT
(
!
active_sub_
);
active_key_
=
next_key_
++
;
using
fwd_t
=
forwarder
<
T
,
concat_sub
,
size_t
>
;
auto
fwd
=
make_counted
<
fwd_t
>
(
this
,
active_key_
);
what
.
subscribe
(
fwd
->
as_observer
());
}
void
subscribe_to
(
observable
<
observable
<
T
>>
what
)
{
CAF_ASSERT
(
!
active_sub_
);
CAF_ASSERT
(
!
factory_sub_
);
factory_key_
=
next_key_
++
;
using
fwd_t
=
forwarder
<
observable
<
T
>
,
concat_sub
,
size_t
>
;
auto
fwd
=
make_counted
<
fwd_t
>
(
this
,
factory_key_
);
what
.
subscribe
(
fwd
->
as_observer
());
}
void
subscribe_next
()
{
if
(
factory_key_
!=
0
)
{
// Ask the factory for the next observable.
CAF_ASSERT
(
!
active_sub_
);
factory_sub_
.
request
(
1
);
}
else
if
(
!
inputs_
.
empty
())
{
// Subscribe to the next observable from the list.
std
::
visit
([
this
](
auto
&
x
)
{
this
->
subscribe_to
(
x
);
},
inputs_
.
front
());
inputs_
.
erase
(
inputs_
.
begin
());
}
else
{
// Done!
fin
();
}
}
// -- callbacks for the forwarders -------------------------------------------
void
fwd_on_subscribe
(
input_key
key
,
subscription
sub
)
{
if
(
active_key_
==
key
&&
!
active_sub_
)
{
active_sub_
=
std
::
move
(
sub
);
if
(
in_flight_
>
0
)
active_sub_
.
request
(
in_flight_
);
}
else
if
(
factory_key_
==
key
&&
!
factory_sub_
)
{
CAF_ASSERT
(
!
active_sub_
);
factory_sub_
=
std
::
move
(
sub
);
factory_sub_
.
request
(
1
);
}
else
{
sub
.
cancel
();
}
}
void
fwd_on_complete
(
input_key
key
)
{
if
(
active_key_
==
key
&&
active_sub_
)
{
active_sub_
=
nullptr
;
subscribe_next
();
}
else
if
(
factory_key_
==
key
&&
factory_sub_
)
{
factory_sub_
=
nullptr
;
factory_key_
=
0
;
if
(
!
active_sub_
)
subscribe_next
();
}
}
void
fwd_on_error
(
input_key
key
,
const
error
&
what
)
{
if
(
key
==
active_key_
||
key
==
factory_key_
)
{
CAF_ASSERT
(
out_
);
if
(
delay_error_
)
{
if
(
!
err_
)
err_
=
what
;
subscribe_next
();
}
else
{
err_
=
what
;
fin
();
}
}
}
void
fwd_on_next
(
input_key
key
,
const
T
&
item
)
{
if
(
key
==
active_key_
)
{
CAF_ASSERT
(
out_
);
--
in_flight_
;
out_
.
on_next
(
item
);
}
}
void
fwd_on_next
(
input_key
key
,
const
observable
<
T
>&
item
)
{
if
(
key
==
factory_key_
)
{
CAF_ASSERT
(
!
active_sub_
);
subscribe_to
(
item
);
}
}
// -- implementation of subscription -----------------------------------------
bool
disposed
()
const
noexcept
override
{
return
!
out_
;
}
void
cancel
()
override
{
if
(
out_
)
{
ctx_
->
delay_fn
([
strong_this
=
intrusive_ptr
<
concat_sub
>
{
this
}]
{
if
(
strong_this
->
out_
)
{
strong_this
->
err_
.
reset
();
strong_this
->
fin
();
}
});
}
}
void
request
(
size_t
n
)
override
{
CAF_ASSERT
(
out_
.
valid
());
if
(
active_sub_
)
active_sub_
.
request
(
n
);
in_flight_
+=
n
;
}
private:
void
fin
()
{
CAF_ASSERT
(
out_
);
if
(
factory_sub_
)
{
factory_sub_
.
cancel
();
factory_sub_
=
nullptr
;
}
if
(
active_sub_
)
{
active_sub_
.
cancel
();
active_sub_
=
nullptr
;
}
factory_key_
=
0
;
active_key_
=
0
;
if
(
err_
)
out_
.
on_error
(
err_
);
else
out_
.
on_complete
();
out_
=
nullptr
;
}
/// Stores the context (coordinator) that runs this flow.
coordinator
*
ctx_
;
/// Stores a handle to the subscribed observer.
observer
<
T
>
out_
;
/// Configures whether we carry on after an error.
bool
delay_error_
=
false
;
/// Caches an on_error reason if delay_error_ is true.
error
err_
;
/// Stores our input sources. The first input is active (subscribed to) while
/// the others are pending (not subscribed to).
std
::
vector
<
input_type
>
inputs_
;
/// If set, identifies the subscription to an observable factory.
subscription
factory_sub_
;
/// Our currently active subscription.
subscription
active_sub_
;
/// Identifies the active forwarder.
input_key
factory_key_
=
0
;
/// Identifies the active forwarder.
input_key
active_key_
=
0
;
/// Stores the next available key.
input_key
next_key_
=
1
;
/// Stores how much demand we have left. When switching to a new input, we
/// pass any demand unused by the previous input to the new one.
size_t
in_flight_
=
0
;
};
template
<
class
T
>
class
concat
:
public
cold
<
T
>
{
public:
// -- member types -----------------------------------------------------------
using
super
=
cold
<
T
>
;
using
input_type
=
std
::
variant
<
observable
<
T
>
,
observable
<
observable
<
T
>>>
;
// -- constructors, destructors, and assignment operators --------------------
template
<
class
...
Ts
,
class
...
Inputs
>
explicit
concat
(
coordinator
*
ctx
,
Inputs
&&
...
inputs
)
:
super
(
ctx
)
{
(
add
(
std
::
forward
<
Inputs
>
(
inputs
)),
...);
}
// -- properties -------------------------------------------------------------
size_t
inputs
()
const
noexcept
{
return
inputs_
.
size
();
}
// -- implementation of observable<T> -----------------------------------
disposable
subscribe
(
observer
<
T
>
out
)
override
{
if
(
inputs
()
==
0
)
{
return
make_counted
<
empty
<
T
>>
(
super
::
ctx_
)
->
subscribe
(
std
::
move
(
out
));
}
else
{
auto
ptr
=
make_counted
<
concat_sub
<
T
>>
(
super
::
ctx_
,
out
,
inputs_
);
out
.
on_subscribe
(
subscription
{
ptr
});
return
ptr
->
as_disposable
();
}
}
private:
template
<
class
Input
>
void
add
(
Input
&&
x
)
{
using
input_t
=
std
::
decay_t
<
Input
>
;
if
constexpr
(
detail
::
is_iterable_v
<
input_t
>
)
{
for
(
auto
&
in
:
x
)
add
(
in
);
}
else
{
static_assert
(
is_observable_v
<
input_t
>
);
inputs_
.
emplace_back
(
std
::
move
(
x
).
as_observable
());
}
}
std
::
vector
<
input_type
>
inputs_
;
};
}
// namespace caf::flow::op
libcaf_core/caf/flow/op/defer.hpp
0 → 100644
View file @
04e02412
// 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/flow/observer.hpp"
#include "caf/flow/op/cold.hpp"
#include "caf/flow/subscription.hpp"
#include <utility>
namespace
caf
::
flow
::
op
{
template
<
class
Fn
>
struct
defer_trait
{
using
fn_result_type
=
decltype
(
std
::
declval
<
Fn
&>
()());
static_assert
(
is_observable_v
<
fn_result_type
>
);
using
output_type
=
typename
fn_result_type
::
output_type
;
};
/// Implementation of the `defer` operator.
template
<
class
Factory
>
class
defer
:
public
cold
<
typename
defer_trait
<
Factory
>::
output_type
>
{
public:
// -- member types -----------------------------------------------------------
using
output_type
=
typename
defer_trait
<
Factory
>::
output_type
;
using
super
=
cold
<
output_type
>
;
// -- constructors, destructors, and assignment operators --------------------
defer
(
coordinator
*
ctx
,
Factory
fn
)
:
super
(
ctx
),
fn_
(
std
::
move
(
fn
))
{
// nop
}
// -- implementation of observable<T>::impl ----------------------------------
disposable
subscribe
(
observer
<
output_type
>
what
)
override
{
return
fn_
().
subscribe
(
what
);
}
private:
Factory
fn_
;
};
}
// namespace caf::flow::op
libcaf_core/caf/flow/op/empty.hpp
0 → 100644
View file @
04e02412
// 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/flow/observable.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/op/cold.hpp"
#include "caf/flow/subscription.hpp"
#include <cstdint>
namespace
caf
::
flow
::
op
{
template
<
class
T
>
class
empty_sub
:
public
subscription
::
impl_base
{
public:
// -- constructors, destructors, and assignment operators --------------------
empty_sub
(
coordinator
*
ctx
,
observer
<
T
>
out
)
:
ctx_
(
ctx
),
out_
(
std
::
move
(
out
))
{
// nop
}
// -- implementation of subscription -----------------------------------------
bool
disposed
()
const
noexcept
override
{
return
!
out_
;
}
void
cancel
()
override
{
if
(
out_
)
ctx_
->
delay_fn
([
out
=
std
::
move
(
out_
)]()
mutable
{
out
.
on_complete
();
});
}
void
request
(
size_t
)
override
{
cancel
();
}
private:
/// Stores the context (coordinator) that runs this flow.
coordinator
*
ctx_
;
/// Stores a handle to the subscribed observer.
observer
<
T
>
out_
;
};
/// An observable that represents an empty range. As soon as an observer
/// requests values from this observable, it calls `on_complete`.
template
<
class
T
>
class
empty
:
public
cold
<
T
>
{
public:
// -- member types -----------------------------------------------------------
using
super
=
cold
<
T
>
;
using
output_type
=
T
;
// -- constructors, destructors, and assignment operators --------------------
explicit
empty
(
coordinator
*
ctx
)
:
super
(
ctx
)
{
// nop
}
// -- implementation of observable<T>::impl ----------------------------------
disposable
subscribe
(
observer
<
output_type
>
out
)
override
{
auto
ptr
=
make_counted
<
empty_sub
<
T
>>
(
super
::
ctx_
,
out
);
out
.
on_subscribe
(
subscription
{
ptr
});
return
disposable
{
std
::
move
(
ptr
)};
}
};
}
// namespace caf::flow::op
libcaf_core/caf/flow/op/fail.hpp
0 → 100644
View file @
04e02412
// 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/observable.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/op/cold.hpp"
#include "caf/flow/subscription.hpp"
namespace
caf
::
flow
::
op
{
template
<
class
T
>
class
fail
:
public
cold
<
T
>
{
public:
// -- member types -----------------------------------------------------------
using
super
=
cold
<
T
>
;
// -- constructors, destructors, and assignment operators --------------------
fail
(
coordinator
*
ctx
,
error
err
)
:
super
(
ctx
),
err_
(
std
::
move
(
err
))
{
// nop
}
// -- implementation of observable_impl<T> -----------------------------------
disposable
subscribe
(
observer
<
T
>
out
)
override
{
out
.
on_error
(
err_
);
return
{};
}
private:
error
err_
;
};
}
// namespace caf::flow::op
libcaf_core/caf/flow/op/from_generator.hpp
0 → 100644
View file @
04e02412
// 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/ref_counted_base.hpp"
#include "caf/detail/type_list.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/op/hot.hpp"
#include "caf/flow/subscription.hpp"
#include "caf/sec.hpp"
#include <tuple>
#include <utility>
namespace
caf
::
flow
::
op
{
template
<
class
Generator
,
class
...
Steps
>
class
from_generator_sub
:
public
subscription
::
impl_base
{
public:
// -- member types -----------------------------------------------------------
using
output_type
=
steps_output_type_t
<
Generator
,
Steps
...
>
;
// -- constructors, destructors, and assignment operators --------------------
from_generator_sub
(
coordinator
*
ctx
,
observer
<
output_type
>
out
,
const
Generator
&
gen
,
const
std
::
tuple
<
Steps
...
>&
steps
)
:
ctx_
(
ctx
),
out_
(
std
::
move
(
out
)),
gen_
(
gen
),
steps_
(
steps
)
{
// nop
}
// -- callbacks for the generator / steps ------------------------------------
bool
on_next
(
const
output_type
&
item
)
{
buf_
.
push_back
(
item
);
return
true
;
}
void
on_complete
()
{
completed_
=
true
;
}
void
on_error
(
const
error
&
what
)
{
completed_
=
true
;
err_
=
what
;
}
// -- implementation of subscription -----------------------------------------
bool
disposed
()
const
noexcept
override
{
return
!
out_
;
}
void
cancel
()
override
{
if
(
out_
)
{
completed_
=
true
;
buf_
.
clear
();
run_later
();
}
}
void
request
(
size_t
n
)
override
{
CAF_ASSERT
(
out_
.
valid
());
demand_
+=
n
;
run_later
();
}
private:
void
run_later
()
{
if
(
!
running_
)
{
running_
=
true
;
ctx_
->
delay_fn
([
strong_this
=
intrusive_ptr
<
from_generator_sub
>
{
this
}]
{
strong_this
->
do_run
();
});
}
}
void
do_run
()
{
while
(
out_
&&
demand_
>
0
)
{
while
(
buf_
.
empty
()
&&
!
completed_
)
pull
(
demand_
);
if
(
!
buf_
.
empty
())
{
--
demand_
;
auto
tmp
=
std
::
move
(
buf_
.
front
());
buf_
.
pop_front
();
out_
.
on_next
(
tmp
);
}
else
if
(
completed_
)
{
fin
();
running_
=
false
;
return
;
}
}
if
(
out_
&&
buf_
.
empty
()
&&
completed_
)
fin
();
running_
=
false
;
}
void
fin
()
{
if
(
!
err_
)
out_
.
on_complete
();
else
out_
.
on_error
(
err_
);
out_
=
nullptr
;
}
void
pull
(
size_t
n
)
{
auto
fn
=
[
this
,
n
](
auto
&
...
steps
)
{
gen_
.
pull
(
n
,
steps
...,
*
this
);
};
std
::
apply
(
fn
,
steps_
);
}
coordinator
*
ctx_
;
bool
running_
=
false
;
std
::
deque
<
output_type
>
buf_
;
bool
completed_
=
false
;
error
err_
;
size_t
demand_
=
0
;
observer
<
output_type
>
out_
;
Generator
gen_
;
std
::
tuple
<
Steps
...
>
steps_
;
};
template
<
class
Generator
,
class
...
Steps
>
using
from_generator_output_t
=
//
typename
detail
::
tl_back_t
<
//
detail
::
type_list
<
Generator
,
Steps
...
>
//
>::
output_type
;
template
<
class
Generator
,
class
...
Steps
>
class
from_generator
:
public
op
::
cold
<
from_generator_output_t
<
Generator
,
Steps
...
>>
{
public:
using
output_type
=
from_generator_output_t
<
Generator
,
Steps
...
>
;
using
super
=
op
::
cold
<
output_type
>
;
from_generator
(
coordinator
*
ctx
,
Generator
gen
,
std
::
tuple
<
Steps
...
>
steps
)
:
super
(
ctx
),
gen_
(
std
::
move
(
gen
)),
steps_
(
std
::
move
(
steps
))
{
// nop
}
// -- implementation of observable_impl<T> ---------------------------------
disposable
subscribe
(
observer
<
output_type
>
out
)
override
{
CAF_LOG_TRACE
(
CAF_ARG2
(
"out"
,
out
.
ptr
()));
using
impl_t
=
from_generator_sub
<
Generator
,
Steps
...
>
;
auto
ptr
=
make_counted
<
impl_t
>
(
super
::
ctx_
,
out
,
gen_
,
steps_
);
auto
sub
=
subscription
{
std
::
move
(
ptr
)};
out
.
on_subscribe
(
sub
);
return
std
::
move
(
sub
).
as_disposable
();
}
private:
Generator
gen_
;
std
::
tuple
<
Steps
...
>
steps_
;
};
}
// namespace caf::flow::op
libcaf_core/caf/flow/op/from_resource.hpp
0 → 100644
View file @
04e02412
// 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/ref_counted_base.hpp"
#include "caf/detail/scope_guard.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
{
/// Reads from an observable buffer and emits the consumed items.
template
<
class
Buffer
>
class
from_resource_sub
:
public
subscription
::
impl_base
,
public
async
::
consumer
{
public:
// -- intrusive_ptr interface ------------------------------------------------
friend
void
intrusive_ptr_add_ref
(
const
from_resource_sub
*
ptr
)
noexcept
{
ptr
->
ref
();
}
friend
void
intrusive_ptr_release
(
const
from_resource_sub
*
ptr
)
noexcept
{
ptr
->
deref
();
}
// -- member types -----------------------------------------------------------
using
value_type
=
typename
Buffer
::
value_type
;
using
buffer_ptr
=
intrusive_ptr
<
Buffer
>
;
// -- constructors, destructors, and assignment operators --------------------
from_resource_sub
(
coordinator
*
ctx
,
buffer_ptr
buf
,
observer
<
value_type
>
out
)
:
ctx_
(
ctx
),
buf_
(
buf
),
out_
(
std
::
move
(
out
))
{
ctx_
->
ref_coordinator
();
}
~
from_resource_sub
()
{
if
(
buf_
)
buf_
->
cancel
();
ctx_
->
deref_coordinator
();
}
// -- implementation of subscription_impl ------------------------------------
bool
disposed
()
const
noexcept
override
{
return
disposed_
;
}
void
cancel
()
override
{
CAF_LOG_TRACE
(
""
);
if
(
!
disposed_
)
{
disposed_
=
true
;
if
(
!
running_
)
do_cancel
();
}
}
void
request
(
size_t
n
)
override
{
CAF_LOG_TRACE
(
CAF_ARG
(
n
));
if
(
demand_
!=
0
)
{
demand_
+=
n
;
}
else
{
demand_
=
n
;
run_later
();
}
}
// -- implementation of async::consumer: these may get called concurrently ---
void
on_producer_ready
()
override
{
// nop
}
void
on_producer_wakeup
()
override
{
CAF_LOG_TRACE
(
""
);
ctx_
->
schedule_fn
([
ptr
=
strong_this
()]
{
CAF_LOG_TRACE
(
""
);
ptr
->
running_
=
true
;
ptr
->
do_run
();
});
}
void
ref_consumer
()
const
noexcept
override
{
this
->
ref
();
}
void
deref_consumer
()
const
noexcept
override
{
this
->
deref
();
}
private:
void
run_later
()
{
if
(
!
running_
)
{
running_
=
true
;
ctx_
->
delay_fn
([
ptr
=
strong_this
()]
{
ptr
->
do_run
();
});
}
}
void
do_cancel
()
{
if
(
buf_
)
{
buf_
->
cancel
();
buf_
=
nullptr
;
}
if
(
out_
)
{
out_
.
on_complete
();
out_
=
nullptr
;
}
}
void
do_run
()
{
CAF_LOG_TRACE
(
""
);
auto
guard
=
detail
::
make_scope_guard
([
this
]
{
running_
=
false
;
});
if
(
disposed_
)
{
do_cancel
();
return
;
}
CAF_ASSERT
(
out_
);
CAF_ASSERT
(
buf_
);
while
(
demand_
>
0
)
{
auto
[
again
,
pulled
]
=
buf_
->
pull
(
async
::
delay_errors
,
demand_
,
out_
);
if
(
!
again
)
{
buf_
=
nullptr
;
out_
=
nullptr
;
disposed_
=
true
;
return
;
}
else
if
(
disposed_
)
{
do_cancel
();
return
;
}
else
if
(
pulled
==
0
)
{
return
;
}
else
{
CAF_ASSERT
(
demand_
>=
pulled
);
demand_
-=
pulled
;
}
}
}
intrusive_ptr
<
from_resource_sub
>
strong_this
()
{
return
{
this
};
}
/// Stores the context (coordinator) that runs this flow. Unlike other
/// observables, we need a strong reference to the context because otherwise
/// the buffer might call schedule_fn on a destroyed object.
intrusive_ptr
<
coordinator
>
ctx_
;
/// Stores a pointer to the asynchronous input buffer.
buffer_ptr
buf_
;
/// Stores a pointer to the target observer running on `remote_ctx_`.
observer
<
value_type
>
out_
;
/// Stores whether do_run is currently running.
bool
running_
=
false
;
/// Stores whether dispose() has been called.
bool
disposed_
=
false
;
/// Stores the demand from the observer.
size_t
demand_
=
0
;
};
template
<
class
T
>
class
from_resource
:
public
hot
<
T
>
{
public:
// -- member types -----------------------------------------------------------
using
resource_type
=
async
::
consumer_resource
<
T
>
;
using
super
=
hot
<
T
>
;
// -- constructors, destructors, and assignment operators --------------------
from_resource
(
coordinator
*
ctx
,
resource_type
resource
)
:
super
(
ctx
),
resource_
(
std
::
move
(
resource
))
{
// nop
}
// -- implementation of observable_impl<T> -----------------------------------
disposable
subscribe
(
observer
<
T
>
out
)
override
{
CAF_LOG_TRACE
(
""
);
CAF_ASSERT
(
out
);
if
(
resource_
)
{
if
(
auto
buf
=
resource_
.
try_open
())
{
resource_
=
nullptr
;
using
buffer_type
=
typename
resource_type
::
buffer_type
;
CAF_LOG_DEBUG
(
"add subscriber"
);
using
impl_t
=
from_resource_sub
<
buffer_type
>
;
auto
ptr
=
make_counted
<
impl_t
>
(
super
::
ctx_
,
buf
,
out
);
buf
->
set_consumer
(
ptr
);
super
::
ctx_
->
watch
(
ptr
->
as_disposable
());
out
.
on_subscribe
(
subscription
{
ptr
});
return
ptr
->
as_disposable
();
}
else
{
resource_
=
nullptr
;
CAF_LOG_WARNING
(
"failed to open an async resource"
);
auto
err
=
make_error
(
sec
::
cannot_open_resource
,
"failed to open an async resource"
);
out
.
on_error
(
err
);
return
disposable
{};
}
}
else
{
CAF_LOG_WARNING
(
"may only subscribe once to an async resource"
);
auto
err
=
make_error
(
sec
::
too_many_observers
,
"may only subscribe once to an async resource"
);
out
.
on_error
(
err
);
return
disposable
{};
}
}
private:
resource_type
resource_
;
};
}
// namespace caf::flow::op
libcaf_core/caf/flow/op/from_steps.hpp
0 → 100644
View file @
04e02412
// 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/defaults.hpp"
#include "caf/detail/ref_counted_base.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/detail/type_list.hpp"
#include <deque>
#include <tuple>
#include <utility>
namespace
caf
::
flow
::
op
{
template
<
class
...
Steps
>
using
from_steps_output_t
=
typename
detail
::
tl_back_t
<
detail
::
type_list
<
Steps
...
>>::
output_type
;
template
<
class
Input
,
class
...
Steps
>
class
from_steps_sub
:
public
detail
::
ref_counted_base
,
public
observer_impl
<
Input
>
,
public
subscription_impl
{
public:
// -- member types -----------------------------------------------------------
using
output_type
=
from_steps_output_t
<
Steps
...
>
;
struct
term_step
;
friend
struct
term_step
;
struct
term_step
{
from_steps_sub
*
sub
;
bool
on_next
(
const
output_type
&
next
)
{
sub
->
buf_
.
push_back
(
next
);
return
true
;
}
void
on_complete
()
{
CAF_ASSERT
(
sub
->
in_
.
valid
());
sub
->
in_
.
cancel
();
sub
->
in_
=
nullptr
;
}
void
on_error
(
const
error
&
what
)
{
CAF_ASSERT
(
sub
->
subscribed
());
sub
->
in_
.
cancel
();
sub
->
in_
=
nullptr
;
sub
->
err_
=
what
;
}
};
// -- constructors, destructors, and assignment operators --------------------
from_steps_sub
(
coordinator
*
ctx
,
observer
<
output_type
>
out
,
std
::
tuple
<
Steps
...
>
steps
)
:
ctx_
(
ctx
),
out_
(
std
::
move
(
out
)),
steps_
(
std
::
move
(
steps
))
{
// nop
}
// -- ref counting -----------------------------------------------------------
void
ref_disposable
()
const
noexcept
final
{
this
->
ref
();
}
void
deref_disposable
()
const
noexcept
final
{
this
->
deref
();
}
void
ref_coordinated
()
const
noexcept
final
{
this
->
ref
();
}
void
deref_coordinated
()
const
noexcept
final
{
this
->
deref
();
}
friend
void
intrusive_ptr_add_ref
(
const
from_steps_sub
*
ptr
)
noexcept
{
ptr
->
ref
();
}
friend
void
intrusive_ptr_release
(
const
from_steps_sub
*
ptr
)
noexcept
{
ptr
->
deref
();
}
// -- properties -------------------------------------------------------------
bool
subscribed
()
const
noexcept
{
return
in_
.
valid
();
}
const
error
&
fail_reason
()
const
{
return
err_
;
}
bool
idle
()
{
return
demand_
>
0
&&
buf_
.
empty
();
}
// -- implementation of observer_impl<Input> ---------------------------------
void
on_next
(
const
Input
&
item
)
override
{
CAF_ASSERT
(
!
in_
||
in_flight_
>
0
);
if
(
in_
)
{
--
in_flight_
;
auto
fn
=
[
this
,
&
item
](
auto
&
step
,
auto
&
...
steps
)
{
term_step
term
{
this
};
step
.
on_next
(
item
,
steps
...,
term
);
};
std
::
apply
(
fn
,
steps_
);
pull
();
if
(
!
running_
)
{
running_
=
true
;
do_run
();
}
}
}
void
on_complete
()
override
{
if
(
in_
)
{
auto
fn
=
[
this
](
auto
&
step
,
auto
&
...
steps
)
{
term_step
term
{
this
};
step
.
on_complete
(
steps
...,
term
);
};
std
::
apply
(
fn
,
steps_
);
if
(
!
running_
)
{
running_
=
true
;
do_run
();
}
}
}
void
on_error
(
const
error
&
what
)
override
{
if
(
in_
)
{
auto
fn
=
[
this
,
&
what
](
auto
&
step
,
auto
&
...
steps
)
{
term_step
term
{
this
};
step
.
on_error
(
what
,
steps
...,
term
);
};
std
::
apply
(
fn
,
steps_
);
if
(
!
running_
)
{
running_
=
true
;
do_run
();
}
}
}
void
on_subscribe
(
subscription
in
)
override
{
if
(
!
in_
)
{
in_
=
std
::
move
(
in
);
pull
();
}
else
{
in
.
cancel
();
}
}
// -- implementation of subscription_impl ------------------------------------
bool
disposed
()
const
noexcept
override
{
return
disposed_
;
}
void
cancel
()
override
{
CAF_LOG_TRACE
(
""
);
if
(
!
disposed_
)
{
disposed_
=
true
;
demand_
=
0
;
buf_
.
clear
();
ctx_
->
delay_fn
([
out
=
std
::
move
(
out_
)]()
mutable
{
out
.
on_complete
();
});
}
if
(
in_
)
{
in_
.
cancel
();
in_
=
nullptr
;
}
}
void
request
(
size_t
n
)
override
{
CAF_LOG_TRACE
(
CAF_ARG
(
n
));
if
(
demand_
!=
0
)
{
demand_
+=
n
;
}
else
{
demand_
=
n
;
run_later
();
}
}
private:
void
pull
()
{
if
(
auto
pending
=
buf_
.
size
()
+
in_flight_
;
in_
&&
pending
<
max_buf_size_
)
{
auto
new_demand
=
max_buf_size_
-
pending
;
in_flight_
+=
new_demand
;
in_
.
request
(
new_demand
);
}
}
void
run_later
()
{
if
(
!
running_
)
{
running_
=
true
;
ctx_
->
delay_fn
([
ptr
=
strong_this
()]
{
ptr
->
do_run
();
});
}
}
void
do_run
()
{
auto
guard
=
detail
::
make_scope_guard
([
this
]
{
running_
=
false
;
});
if
(
!
disposed_
)
{
CAF_ASSERT
(
out_
);
while
(
demand_
>
0
&&
!
buf_
.
empty
())
{
auto
item
=
std
::
move
(
buf_
.
front
());
buf_
.
pop_front
();
--
demand_
;
out_
.
on_next
(
item
);
}
if
(
!
in_
&&
buf_
.
empty
())
{
if
(
err_
)
out_
.
on_error
(
err_
);
else
out_
.
on_complete
();
out_
=
nullptr
;
disposed_
=
true
;
}
}
}
intrusive_ptr
<
from_steps_sub
>
strong_this
()
{
return
{
this
};
}
coordinator
*
ctx_
;
subscription
in_
;
observer
<
output_type
>
out_
;
std
::
tuple
<
Steps
...
>
steps_
;
std
::
deque
<
output_type
>
buf_
;
size_t
demand_
=
0
;
size_t
in_flight_
=
0
;
size_t
max_buf_size_
=
defaults
::
flow
::
buffer_size
;
bool
disposed_
=
false
;
bool
running_
=
false
;
error
err_
;
};
template
<
class
Input
,
class
...
Steps
>
class
from_steps
:
public
op
::
cold
<
from_steps_output_t
<
Steps
...
>>
{
public:
// -- member types -----------------------------------------------------------
static_assert
(
sizeof
...(
Steps
)
>
0
);
using
input_type
=
Input
;
using
output_type
=
from_steps_output_t
<
Steps
...
>
;
using
super
=
op
::
cold
<
output_type
>
;
// -- constructors, destructors, and assignment operators --------------------
from_steps
(
coordinator
*
ctx
,
intrusive_ptr
<
base
<
input_type
>>
input
,
std
::
tuple
<
Steps
...
>
steps
)
:
super
(
ctx
),
input_
(
std
::
move
(
input
)),
steps_
(
std
::
move
(
steps
))
{
// nop
}
// -- implementation of observable_impl<T> -----------------------------------
disposable
subscribe
(
observer
<
output_type
>
out
)
override
{
CAF_LOG_TRACE
(
CAF_ARG2
(
"out"
,
out
.
ptr
()));
using
sub_t
=
from_steps_sub
<
Input
,
Steps
...
>
;
auto
ptr
=
make_counted
<
sub_t
>
(
super
::
ctx_
,
out
,
steps_
);
input_
->
subscribe
(
observer
<
input_type
>
{
ptr
});
if
(
ptr
->
subscribed
())
{
auto
sub
=
subscription
{
std
::
move
(
ptr
)};
out
.
on_subscribe
(
sub
);
return
std
::
move
(
sub
).
as_disposable
();
}
else
if
(
auto
&
fail_reason
=
ptr
->
fail_reason
())
{
out
.
on_error
(
fail_reason
);
return
disposable
{};
}
else
{
auto
err
=
make_error
(
sec
::
invalid_observable
,
"flow operator from_steps failed "
"to subscribe to its input"
);
out
.
on_error
(
err
);
return
disposable
{};
}
}
private:
intrusive_ptr
<
base
<
input_type
>>
input_
;
std
::
tuple
<
Steps
...
>
steps_
;
};
}
// namespace caf::flow::op
libcaf_core/caf/flow/op/hot.hpp
0 → 100644
View file @
04e02412
// 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/ref_counted_base.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/op/base.hpp"
#include "caf/flow/subscription.hpp"
namespace
caf
::
flow
::
op
{
/// Convenience base type for *hot* observable types.
template
<
class
T
>
class
hot
:
public
detail
::
ref_counted_base
,
public
base
<
T
>
{
public:
// -- member types -----------------------------------------------------------
using
output_type
=
T
;
// -- constructors, destructors, and assignment operators --------------------
explicit
hot
(
coordinator
*
ctx
)
:
ctx_
(
ctx
)
{
// nop
}
// -- implementation of disposable_impl --------------------------------------
void
ref_coordinated
()
const
noexcept
final
{
this
->
ref
();
}
void
deref_coordinated
()
const
noexcept
final
{
this
->
deref
();
}
// -- implementation of observable_impl<T> -----------------------------------
coordinator
*
ctx
()
const
noexcept
final
{
return
ctx_
;
}
protected:
// -- member variables -------------------------------------------------------
coordinator
*
ctx_
;
};
}
// namespace caf::flow::op
libcaf_core/caf/flow/op/interval.hpp
0 → 100644
View file @
04e02412
// 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/core_export.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/op/cold.hpp"
#include "caf/timespan.hpp"
#include <cstdint>
namespace
caf
::
flow
::
op
{
class
CAF_CORE_EXPORT
interval
:
public
cold
<
int64_t
>
{
public:
// -- member types -----------------------------------------------------------
using
super
=
cold
<
int64_t
>
;
// -- constructors, destructors, and assignment operators --------------------
interval
(
coordinator
*
ctx
,
timespan
initial_delay
,
timespan
period
);
interval
(
coordinator
*
ctx
,
timespan
initial_delay
,
timespan
period
,
int64_t
max_val
);
// -- implementation of observable_impl<T> -----------------------------------
disposable
subscribe
(
observer
<
int64_t
>
out
)
override
;
private:
timespan
initial_delay_
;
timespan
period_
;
int64_t
max_
;
};
}
// namespace caf::flow::op
libcaf_core/caf/flow/op/mcast.hpp
0 → 100644
View file @
04e02412
// 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/flow/coordinator.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/op/empty.hpp"
#include "caf/flow/op/hot.hpp"
#include "caf/flow/subscription.hpp"
#include "caf/make_counted.hpp"
#include <algorithm>
#include <deque>
#include <memory>
namespace
caf
::
flow
::
op
{
/// State shared between one multicast operator and one subscribed observer.
template
<
class
T
>
struct
mcast_sub_state
{
std
::
deque
<
T
>
buf
;
size_t
demand
=
0
;
observer
<
T
>
out
;
bool
disposed
=
false
;
bool
closed
=
false
;
bool
running
=
false
;
error
err
;
void
push
(
const
T
&
item
)
{
if
(
disposed
)
{
// nop
}
else
if
(
demand
>
0
&&
!
running
)
{
CAF_ASSERT
(
out
);
CAF_ASSERT
(
buf
.
empty
());
--
demand
;
out
.
on_next
(
item
);
}
else
{
buf
.
push_back
(
item
);
}
}
void
close
()
{
if
(
!
disposed
)
{
closed
=
true
;
if
(
!
running
&&
buf
.
empty
())
{
disposed
=
true
;
out
.
on_complete
();
}
}
}
void
abort
(
const
error
&
reason
)
{
if
(
!
disposed
&&
!
err
)
{
closed
=
true
;
err
=
reason
;
if
(
!
running
&&
buf
.
empty
())
{
disposed
=
true
;
out
.
on_error
(
reason
);
}
}
}
void
do_dispose
()
{
if
(
out
)
{
out
.
on_complete
();
out
=
nullptr
;
}
buf
.
clear
();
demand
=
0
;
disposed
=
true
;
}
void
do_run
()
{
auto
guard
=
detail
::
make_scope_guard
([
this
]
{
running
=
false
;
});
if
(
!
disposed
)
{
while
(
demand
>
0
&&
!
buf
.
empty
())
{
out
.
on_next
(
buf
.
front
());
buf
.
pop_front
();
--
demand
;
}
if
(
buf
.
empty
()
&&
closed
)
{
disposed
=
true
;
if
(
err
)
out
.
on_error
(
err
);
else
out
.
on_complete
();
}
}
}
};
template
<
class
T
>
using
mcast_sub_state_ptr
=
std
::
shared_ptr
<
mcast_sub_state
<
T
>>
;
template
<
class
T
>
class
mcast_sub
:
public
subscription
::
impl_base
{
public:
// -- constructors, destructors, and assignment operators --------------------
mcast_sub
(
coordinator
*
ctx
,
mcast_sub_state_ptr
<
T
>
state
)
:
ctx_
(
ctx
),
state_
(
std
::
move
(
state
))
{
// nop
}
// -- implementation of subscription -----------------------------------------
bool
disposed
()
const
noexcept
override
{
return
!
state_
;
}
void
cancel
()
override
{
if
(
state_
)
{
ctx_
->
delay_fn
([
state
=
std
::
move
(
state_
)]()
{
state
->
do_dispose
();
});
}
}
void
request
(
size_t
n
)
override
{
state_
->
demand
+=
n
;
if
(
!
state_
->
running
)
{
state_
->
running
=
true
;
ctx_
->
delay_fn
([
state
=
state_
]
{
state
->
do_run
();
});
}
}
private:
/// Stores the context (coordinator) that runs this flow.
coordinator
*
ctx_
;
/// Stores a handle to the state.
mcast_sub_state_ptr
<
T
>
state_
;
};
// Base type for *hot* operators that multicast data to subscribed observers.
template
<
class
T
>
class
mcast
:
public
hot
<
T
>
{
public:
// -- member types -----------------------------------------------------------
using
super
=
hot
<
T
>
;
using
state_type
=
mcast_sub_state
<
T
>
;
using
state_ptr_type
=
mcast_sub_state_ptr
<
T
>
;
using
observer_type
=
observer
<
T
>
;
// -- constructors, destructors, and assignment operators --------------------
explicit
mcast
(
coordinator
*
ctx
)
:
super
(
ctx
)
{
// nop
}
/// Pushes @p item to all subscribers.
void
push_all
(
const
T
&
item
)
{
for
(
auto
&
state
:
states_
)
state
->
push
(
item
);
}
/// Closes the operator, eventually emitting on_complete on all observers.
void
close
()
{
if
(
!
closed_
)
{
closed_
=
true
;
for
(
auto
&
state
:
states_
)
state
->
close
();
states_
.
clear
();
}
}
/// Closes the operator, eventually emitting on_error on all observers.
void
abort
(
const
error
&
reason
)
{
if
(
!
closed_
)
{
closed_
=
true
;
for
(
auto
&
state
:
states_
)
state
->
abort
(
reason
);
states_
.
clear
();
}
}
size_t
max_demand
()
const
noexcept
{
if
(
states_
.
empty
())
{
return
0
;
}
else
{
auto
pred
=
[](
const
state_ptr_type
&
x
,
const
state_ptr_type
&
y
)
{
return
x
->
demand
<
y
->
demand
;
};
return
std
::
max_element
(
states_
.
begin
(),
states_
.
end
(),
pred
)
->
demand
;
}
}
size_t
min_demand
()
const
noexcept
{
if
(
states_
.
empty
())
{
return
0
;
}
else
{
auto
pred
=
[](
const
state_ptr_type
&
x
,
const
state_ptr_type
&
y
)
{
return
x
->
demand
<
y
->
demand
;
};
return
std
::
min_element
(
states_
.
begin
(),
states_
.
end
(),
pred
)
->
demand
;
}
}
size_t
max_buffered
()
const
noexcept
{
if
(
states_
.
empty
())
{
return
0
;
}
else
{
auto
pred
=
[](
const
state_ptr_type
&
x
,
const
state_ptr_type
&
y
)
{
return
x
->
buf
.
size
()
<
y
->
buf
.
size
();
};
return
std
::
max_element
(
states_
.
begin
(),
states_
.
end
(),
pred
)
->
buf
.
size
();
}
}
size_t
min_buffered
()
const
noexcept
{
if
(
states_
.
empty
())
{
return
0
;
}
else
{
auto
pred
=
[](
const
state_ptr_type
&
x
,
const
state_ptr_type
&
y
)
{
return
x
->
buf
.
size
()
<
y
->
buf
.
size
();
};
return
std
::
min_element
(
states_
.
begin
(),
states_
.
end
(),
pred
)
->
buf
.
size
();
}
}
/// Queries whether there is at least one observer subscribed to the operator.
bool
has_observers
()
const
noexcept
{
return
!
states_
.
empty
();
}
state_ptr_type
add_state
(
observer_type
out
)
{
auto
state
=
std
::
make_shared
<
state_type
>
();
state
->
out
=
std
::
move
(
out
);
states_
.
push_back
(
state
);
return
state
;
}
disposable
subscribe
(
observer
<
T
>
out
)
override
{
if
(
!
closed_
)
{
auto
ptr
=
make_counted
<
mcast_sub
<
T
>>
(
super
::
ctx_
,
add_state
(
out
));
out
.
on_subscribe
(
subscription
{
ptr
});
return
disposable
{
std
::
move
(
ptr
)};
}
else
if
(
!
err_
)
{
return
make_counted
<
op
::
empty
<
T
>>
(
super
::
ctx_
)
->
subscribe
(
out
);
}
else
{
out
.
on_error
(
err_
);
return
disposable
{};
}
}
protected:
bool
closed_
=
false
;
error
err_
;
std
::
vector
<
mcast_sub_state_ptr
<
T
>>
states_
;
};
}
// namespace caf::flow::op
libcaf_core/caf/flow/op/merge.hpp
0 → 100644
View file @
04e02412
// 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/ref_counted_base.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/op/cold.hpp"
#include "caf/flow/op/empty.hpp"
#include "caf/flow/subscription.hpp"
#include "caf/make_counted.hpp"
#include <deque>
#include <memory>
#include <numeric>
#include <utility>
#include <variant>
#include <vector>
namespace
caf
::
flow
::
op
{
/// @relates merge
template
<
class
T
>
struct
merge_input
{
/// The subscription to this input.
subscription
sub
;
/// Stores received items until the merge can forward them downstream.
std
::
deque
<
T
>
buf
;
};
template
<
class
T
>
class
merge_sub
:
public
subscription
::
impl_base
{
public:
// -- member types -----------------------------------------------------------
using
input_t
=
merge_input
<
T
>
;
using
input_key
=
size_t
;
using
input_ptr
=
std
::
unique_ptr
<
input_t
>
;
using
input_map
=
detail
::
unordered_flat_map
<
input_key
,
input_ptr
>
;
// -- constructors, destructors, and assignment operators --------------------
merge_sub
(
coordinator
*
ctx
,
observer
<
T
>
out
)
:
ctx_
(
ctx
),
out_
(
std
::
move
(
out
))
{
// nop
}
// -- input management -------------------------------------------------------
void
subscribe_to
(
observable
<
T
>
what
)
{
auto
key
=
next_key_
++
;
inputs_
.
container
().
emplace_back
(
key
,
std
::
make_unique
<
input_t
>
());
using
fwd_impl
=
forwarder
<
T
,
merge_sub
,
size_t
>
;
auto
fwd
=
make_counted
<
fwd_impl
>
(
this
,
key
);
what
.
subscribe
(
fwd
->
as_observer
());
}
void
subscribe_to
(
observable
<
observable
<
T
>>
what
)
{
auto
key
=
next_key_
++
;
auto
&
vec
=
inputs_
.
container
();
vec
.
emplace_back
(
key
,
std
::
make_unique
<
input_t
>
());
using
fwd_impl
=
forwarder
<
observable
<
T
>
,
merge_sub
,
size_t
>
;
auto
fwd
=
make_counted
<
fwd_impl
>
(
this
,
key
);
what
.
subscribe
(
fwd
->
as_observer
());
}
// -- callbacks for the forwarders -------------------------------------------
void
fwd_on_subscribe
(
input_key
key
,
subscription
sub
)
{
CAF_LOG_TRACE
(
CAF_ARG
(
key
));
if
(
auto
ptr
=
get
(
key
);
ptr
&&
!
ptr
->
sub
&&
out_
)
{
sub
.
request
(
max_pending_
);
ptr
->
sub
=
std
::
move
(
sub
);
}
else
{
sub
.
cancel
();
}
}
void
fwd_on_complete
(
input_key
key
)
{
CAF_LOG_TRACE
(
CAF_ARG
(
key
));
if
(
auto
i
=
inputs_
.
find
(
key
);
i
!=
inputs_
.
end
())
{
if
(
i
->
second
->
buf
.
empty
())
{
inputs_
.
erase
(
i
);
run_later
();
}
else
{
i
->
second
->
sub
=
nullptr
;
}
}
}
void
fwd_on_error
(
input_key
key
,
const
error
&
what
)
{
CAF_LOG_TRACE
(
CAF_ARG
(
key
)
<<
CAF_ARG
(
what
));
if
(
!
err_
)
{
err_
=
what
;
if
(
!
flags_
.
delay_error
)
{
auto
i
=
inputs_
.
begin
();
while
(
i
!=
inputs_
.
end
())
{
auto
&
input
=
*
i
->
second
;
if
(
auto
&
sub
=
input
.
sub
)
{
sub
.
cancel
();
sub
=
nullptr
;
}
if
(
input
.
buf
.
empty
())
i
=
inputs_
.
erase
(
i
);
else
++
i
;
}
}
}
fwd_on_complete
(
key
);
}
void
fwd_on_next
(
input_key
key
,
const
T
&
item
)
{
CAF_LOG_TRACE
(
CAF_ARG
(
key
)
<<
CAF_ARG
(
item
));
if
(
auto
ptr
=
get
(
key
))
{
ptr
->
buf
.
push_back
(
item
);
if
(
ptr
->
buf
.
size
()
==
1
&&
!
flags_
.
running
)
{
flags_
.
running
=
true
;
do_run
();
}
}
}
void
fwd_on_next
(
input_key
key
,
const
observable
<
T
>&
item
)
{
CAF_LOG_TRACE
(
CAF_ARG
(
key
)
<<
CAF_ARG
(
item
));
if
(
auto
ptr
=
get
(
key
))
subscribe_to
(
item
);
}
// -- implementation of subscription_impl ------------------------------------
bool
disposed
()
const
noexcept
override
{
return
!
out_
;
}
void
cancel
()
override
{
if
(
out_
)
{
for
(
auto
&
kvp
:
inputs_
)
if
(
auto
&
sub
=
kvp
.
second
->
sub
)
sub
.
cancel
();
inputs_
.
clear
();
run_later
();
}
}
void
request
(
size_t
n
)
override
{
CAF_ASSERT
(
out_
.
valid
());
demand_
+=
n
;
run_later
();
}
size_t
buffered
()
const
noexcept
{
return
std
::
accumulate
(
inputs_
.
begin
(),
inputs_
.
end
(),
size_t
{
0
},
[](
size_t
n
,
auto
&
kvp
)
{
return
n
+
kvp
.
second
->
buf
.
size
();
});
}
private:
void
run_later
()
{
if
(
!
flags_
.
running
)
{
flags_
.
running
=
true
;
ctx_
->
delay_fn
([
strong_this
=
intrusive_ptr
<
merge_sub
>
{
this
}]
{
strong_this
->
do_run
();
});
}
}
auto
next_input
()
{
CAF_ASSERT
(
!
inputs_
.
empty
());
auto
has_items_at
=
[
this
](
size_t
pos
)
{
auto
&
vec
=
inputs_
.
container
();
return
!
vec
[
pos
].
second
->
buf
.
empty
();
};
auto
start
=
pos_
%
inputs_
.
size
();
pos_
=
(
pos_
+
1
)
%
inputs_
.
size
();
if
(
has_items_at
(
start
))
return
inputs_
.
begin
()
+
start
;
while
(
pos_
!=
start
)
{
if
(
has_items_at
(
pos_
))
return
inputs_
.
begin
()
+
pos_
;
pos_
=
(
pos_
+
1
)
%
inputs_
.
size
();
}
return
inputs_
.
end
();
}
void
do_run
()
{
while
(
out_
&&
demand_
>
0
&&
!
inputs_
.
empty
())
{
if
(
auto
i
=
next_input
();
i
!=
inputs_
.
end
())
{
--
demand_
;
auto
&
buf
=
i
->
second
->
buf
;
auto
tmp
=
std
::
move
(
buf
.
front
());
buf
.
pop_front
();
if
(
auto
&
sub
=
i
->
second
->
sub
)
sub
.
request
(
1
);
else
if
(
buf
.
empty
())
inputs_
.
erase
(
i
);
out_
.
on_next
(
tmp
);
}
else
{
flags_
.
running
=
false
;
return
;
}
}
if
(
out_
&&
inputs_
.
empty
())
fin
();
flags_
.
running
=
false
;
}
void
fin
()
{
if
(
!
inputs_
.
empty
())
{
for
(
auto
&
kvp
:
inputs_
)
if
(
auto
&
sub
=
kvp
.
second
->
sub
)
sub
.
cancel
();
inputs_
.
clear
();
}
if
(
!
err_
)
{
out_
.
on_complete
();
}
else
{
out_
.
on_error
(
err_
);
}
out_
=
nullptr
;
}
/// Selects an input object by key or returns null.
input_t
*
get
(
input_key
key
)
{
if
(
auto
i
=
inputs_
.
find
(
key
);
i
!=
inputs_
.
end
())
return
i
->
second
.
get
();
else
return
nullptr
;
}
/// Groups various Boolean flags.
struct
flags_t
{
/// Configures whether an error immediately aborts the merging or not.
bool
delay_error
:
1
;
/// Stores whether the merge is currently executing do_run.
bool
running
:
1
;
flags_t
()
:
delay_error
(
false
),
running
(
false
)
{
// nop
}
};
/// Stores the context (coordinator) that runs this flow.
coordinator
*
ctx_
;
/// Stores the first error that occurred on any input.
error
err_
;
/// Fine-tunes the behavior of the merge.
flags_t
flags_
;
/// Stores our current demand for items from the subscriber.
size_t
demand_
=
0
;
/// Stores a handle to the subscriber.
observer
<
T
>
out_
;
/// Stores the last round-robin read position.
size_t
pos_
=
0
;
/// Associates inputs with ascending keys.
input_map
inputs_
;
/// Stores the key for the next input.
size_t
next_key_
=
0
;
/// Configures how many items we buffer per input.
size_t
max_pending_
=
defaults
::
flow
::
buffer_size
;
};
template
<
class
T
>
class
merge
:
public
cold
<
T
>
{
public:
// -- member types -----------------------------------------------------------
using
super
=
cold
<
T
>
;
using
input_type
=
std
::
variant
<
observable
<
T
>
,
observable
<
observable
<
T
>>>
;
// -- constructors, destructors, and assignment operators --------------------
template
<
class
...
Ts
,
class
...
Inputs
>
explicit
merge
(
coordinator
*
ctx
,
Inputs
&&
...
inputs
)
:
super
(
ctx
)
{
(
add
(
std
::
forward
<
Inputs
>
(
inputs
)),
...);
}
// -- properties -------------------------------------------------------------
size_t
inputs
()
const
noexcept
{
return
inputs_
.
size
();
}
// -- implementation of observable_impl<T> -----------------------------------
disposable
subscribe
(
observer
<
T
>
out
)
override
{
if
(
inputs
()
==
0
)
{
auto
ptr
=
make_counted
<
empty
<
T
>>
(
super
::
ctx_
);
return
ptr
->
subscribe
(
std
::
move
(
out
));
}
else
{
auto
sub
=
make_counted
<
merge_sub
<
T
>>
(
super
::
ctx_
,
out
);
for
(
auto
&
input
:
inputs_
)
std
::
visit
([
&
sub
](
auto
&
in
)
{
sub
->
subscribe_to
(
in
);
},
input
);
out
.
on_subscribe
(
subscription
{
sub
});
return
sub
->
as_disposable
();
}
}
private:
template
<
class
Input
>
void
add
(
Input
&&
x
)
{
using
input_t
=
std
::
decay_t
<
Input
>
;
if
constexpr
(
detail
::
is_iterable_v
<
input_t
>
)
{
for
(
auto
&
in
:
x
)
add
(
in
);
}
else
{
static_assert
(
is_observable_v
<
input_t
>
);
inputs_
.
emplace_back
(
std
::
forward
<
Input
>
(
x
).
as_observable
());
}
}
std
::
vector
<
input_type
>
inputs_
;
};
}
// namespace caf::flow::op
libcaf_core/caf/flow/op/never.hpp
0 → 100644
View file @
04e02412
// 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/flow/observer.hpp"
#include "caf/flow/op/cold.hpp"
#include "caf/flow/subscription.hpp"
#include <utility>
namespace
caf
::
flow
::
op
{
template
<
class
T
>
class
never_sub
:
public
subscription
::
impl_base
{
public:
// -- constructors, destructors, and assignment operators --------------------
never_sub
(
coordinator
*
ctx
,
observer
<
T
>
out
)
:
ctx_
(
ctx
),
out_
(
std
::
move
(
out
))
{
// nop
}
// -- implementation of subscription -----------------------------------------
bool
disposed
()
const
noexcept
override
{
return
!
out_
;
}
void
cancel
()
override
{
if
(
out_
)
ctx_
->
delay_fn
([
out
=
std
::
move
(
out_
)]()
mutable
{
out
.
on_complete
();
});
}
void
request
(
size_t
)
override
{
// nop
}
private:
/// Stores the context (coordinator) that runs this flow.
coordinator
*
ctx_
;
/// Stores a handle to the subscribed observer.
observer
<
T
>
out_
;
};
/// An observable that never calls any callbacks on its subscribers.
template
<
class
T
>
class
never
:
public
cold
<
T
>
{
public:
// -- member types -----------------------------------------------------------
using
super
=
cold
<
T
>
;
using
output_type
=
T
;
// -- constructors, destructors, and assignment operators --------------------
explicit
never
(
coordinator
*
ctx
)
:
super
(
ctx
)
{
// nop
}
// -- implementation of observable_impl<T> -----------------------------------
disposable
subscribe
(
observer
<
output_type
>
out
)
override
{
auto
ptr
=
make_counted
<
never_sub
<
T
>>
(
super
::
ctx_
,
out
);
out
.
ptr
()
->
on_subscribe
(
subscription
{
ptr
});
return
ptr
->
as_disposable
();
}
};
}
// namespace caf::flow::op
libcaf_core/caf/flow/op/prefix_and_tail.hpp
0 → 100644
View file @
04e02412
// 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
::
ref_counted_base
,
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
.
cancel
();
}
// -- implementation of subscription_impl ------------------------------------
bool
disposed
()
const
noexcept
override
{
return
!
out_
;
}
void
cancel
()
override
{
if
(
in_
)
{
in_
.
cancel
();
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_
->
cancel
();
}
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
::
ref_counted_base
,
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
libcaf_core/caf/flow/zip_with.hpp
→
libcaf_core/caf/flow/
op/
zip_with.hpp
View file @
04e02412
...
@@ -4,107 +4,90 @@
...
@@ -4,107 +4,90 @@
#pragma once
#pragma once
#include "caf/async/policy.hpp"
#include "caf/detail/ref_counted_base.hpp"
#include "caf/flow/observable.hpp"
#include "caf/flow/observable_decl.hpp"
#include "caf/flow/observable_state.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/op/cold.hpp"
#include "caf/flow/op/empty.hpp"
#include "caf/flow/subscription.hpp"
#include <algorithm>
#include <memory>
#include <tuple>
#include <type_traits>
#include <type_traits>
#include <utility>
#include <utility>
#include <vector>
namespace
caf
::
flow
{
namespace
caf
::
flow
::
op
{
template
<
class
F
,
class
...
Ts
>
template
<
class
F
,
class
...
Ts
>
struct
zip
per
_oracle
{
struct
zip
_wich
_oracle
{
using
output_type
using
output_type
=
decltype
(
std
::
declval
<
F
&>
()(
std
::
declval
<
const
Ts
&>
()...));
=
decltype
(
std
::
declval
<
F
&>
()(
std
::
declval
<
const
Ts
&>
()...));
};
};
template
<
class
F
,
class
...
Ts
>
template
<
class
F
,
class
...
Ts
>
using
zip
per_output_t
=
typename
zipper
_oracle
<
F
,
Ts
...
>::
output_type
;
using
zip
_with_output_t
=
typename
zip_wich
_oracle
<
F
,
Ts
...
>::
output_type
;
template
<
size_t
Index
>
template
<
size_t
Index
>
using
zip
per
_index
=
std
::
integral_constant
<
size_t
,
Index
>
;
using
zip_index
=
std
::
integral_constant
<
size_t
,
Index
>
;
template
<
class
T
>
template
<
class
T
>
struct
zip
per
_input
{
struct
zip_input
{
using
value_type
=
T
;
using
value_type
=
T
;
explicit
zipper_input
(
observable
<
T
>
in
)
:
in
(
std
::
move
(
in
))
{
// nop
}
observable
<
T
>
in
;
subscription
sub
;
subscription
sub
;
std
::
vector
<
T
>
buf
;
std
::
vector
<
T
>
buf
;
bool
broken
=
false
;
/// Returns whether the input can no longer produce additional items.
/// Returns whether the input can no longer produce additional items.
bool
at_end
()
const
noexcept
{
bool
at_end
()
const
noexcept
{
return
broken
&&
buf
.
empty
();
return
!
sub
&&
buf
.
empty
();
}
}
};
};
/// Combines items from any number of observables using a zip function.
/// Combines items from any number of observables using a zip function.
template
<
class
F
,
class
...
Ts
>
template
<
class
F
,
class
...
Ts
>
class
zip
per_impl
:
public
observable_impl_base
<
zipper_output_t
<
F
,
Ts
...
>>
{
class
zip
_with_sub
:
public
subscription
::
impl_base
{
public:
public:
// -- member types -----------------------------------------------------------
// -- member types -----------------------------------------------------------
using
output_type
=
zipper_output_t
<
F
,
Ts
...
>
;
using
output_type
=
zip_with_output_t
<
F
,
Ts
...
>
;
using
super
=
observable_impl_base
<
output_type
>
;
// -- friends ----------------------------------------------------------------
CAF_INTRUSIVE_PTR_FRIENDS
(
zipper_impl
)
template
<
class
,
class
,
class
>
friend
class
forwarder
;
// -- constructors, destructors, and assignment operators --------------------
// -- constructors, destructors, and assignment operators --------------------
zipper_impl
(
coordinator
*
ctx
,
F
fn
,
observable
<
Ts
>
...
inputs
)
zip_with_sub
(
coordinator
*
ctx
,
F
fn
,
observer
<
output_type
>
out
,
:
super
(
ctx
),
fn_
(
std
::
move
(
fn
)),
inputs_
(
inputs
...)
{
std
::
tuple
<
observable
<
Ts
>
...
>&
srcs
)
// nop
:
ctx_
(
ctx
),
fn_
(
std
::
move
(
fn
)),
out_
(
std
::
move
(
out
))
{
for_each_input
([
this
,
&
srcs
](
auto
index
,
auto
&
input
)
{
using
index_type
=
decltype
(
index
);
using
value_type
=
typename
std
::
decay_t
<
decltype
(
input
)
>::
value_type
;
using
fwd_impl
=
forwarder
<
value_type
,
zip_with_sub
,
index_type
>
;
auto
fwd
=
make_counted
<
fwd_impl
>
(
this
,
index
);
std
::
get
<
index_type
::
value
>
(
srcs
).
subscribe
(
fwd
->
as_observer
());
});
}
}
// -- implementation of
disposable::impl
-------------------------------------
// -- implementation of
subscription ----
-------------------------------------
void
dispose
()
override
{
bool
disposed
()
const
noexcept
override
{
if
(
buffered
()
==
0
)
{
return
!
out_
;
fin
();
}
}
else
{
void
cancel
()
override
{
if
(
out_
)
{
for_each_input
([](
auto
,
auto
&
input
)
{
for_each_input
([](
auto
,
auto
&
input
)
{
input
.
broken
=
true
;
input
.
in
=
nullptr
;
if
(
input
.
sub
)
{
if
(
input
.
sub
)
{
input
.
sub
.
cancel
();
input
.
sub
.
cancel
();
input
.
sub
=
nullptr
;
input
.
sub
=
nullptr
;
}
}
// Do not clear the buffer to allow already arrived items to go through.
input
.
buf
.
clear
();
});
}
}
bool
disposed
()
const
noexcept
override
{
return
term_
.
finalized
();
}
// -- implementation of observable<T>::impl ----------------------------------
void
on_request
(
disposable_impl
*
sink
,
size_t
demand
)
override
{
if
(
auto
n
=
term_
.
on_request
(
sink
,
demand
);
n
>
0
)
{
demand_
+=
n
;
for_each_input
([
n
](
auto
,
auto
&
input
)
{
if
(
input
.
sub
)
input
.
sub
.
request
(
n
);
});
});
fin
();
}
}
}
}
void
on_cancel
(
disposable_impl
*
sink
)
override
{
void
request
(
size_t
n
)
override
{
if
(
auto
n
=
term_
.
on_cancel
(
sink
);
n
>
0
)
{
if
(
out_
)
{
demand_
+=
n
;
demand_
+=
n
;
for_each_input
([
n
](
auto
,
auto
&
input
)
{
for_each_input
([
n
](
auto
,
auto
&
input
)
{
if
(
input
.
sub
)
if
(
input
.
sub
)
...
@@ -113,30 +96,16 @@ public:
...
@@ -113,30 +96,16 @@ public:
}
}
}
}
disposable
subscribe
(
observer
<
output_type
>
sink
)
override
{
// -- utility functions ------------------------------------------------------
// On the first subscribe, we subscribe to our inputs.
auto
res
=
term_
.
add
(
this
,
sink
);
if
(
res
&&
term_
.
start
())
{
for_each_input
([
this
](
auto
index
,
auto
&
input
)
{
using
input_t
=
std
::
decay_t
<
decltype
(
input
)
>
;
using
value_t
=
typename
input_t
::
value_type
;
using
fwd_impl
=
forwarder
<
value_t
,
zipper_impl
,
decltype
(
index
)
>
;
auto
fwd
=
make_counted
<
fwd_impl
>
(
this
,
index
);
input
.
in
.
subscribe
(
fwd
->
as_observer
());
});
}
return
res
;
}
private:
template
<
size_t
I
>
template
<
size_t
I
>
auto
&
at
(
zip
per
_index
<
I
>
)
{
auto
&
at
(
zip_index
<
I
>
)
{
return
std
::
get
<
I
>
(
inputs_
);
return
std
::
get
<
I
>
(
inputs_
);
}
}
template
<
class
Fn
,
size_t
...
Is
>
template
<
class
Fn
,
size_t
...
Is
>
void
for_each_input
(
Fn
&&
fn
,
std
::
index_sequence
<
Is
...
>
)
{
void
for_each_input
(
Fn
&&
fn
,
std
::
index_sequence
<
Is
...
>
)
{
(
fn
(
zip
per_index
<
Is
>
{},
at
(
zipper
_index
<
Is
>
{})),
...);
(
fn
(
zip
_index
<
Is
>
{},
at
(
zip
_index
<
Is
>
{})),
...);
}
}
template
<
class
Fn
>
template
<
class
Fn
>
...
@@ -146,7 +115,7 @@ private:
...
@@ -146,7 +115,7 @@ private:
template
<
class
Fn
,
size_t
...
Is
>
template
<
class
Fn
,
size_t
...
Is
>
auto
fold
(
Fn
&&
fn
,
std
::
index_sequence
<
Is
...
>
)
{
auto
fold
(
Fn
&&
fn
,
std
::
index_sequence
<
Is
...
>
)
{
return
fn
(
at
(
zip
per
_index
<
Is
>
{})...);
return
fn
(
at
(
zip_index
<
Is
>
{})...);
}
}
template
<
class
Fn
>
template
<
class
Fn
>
...
@@ -158,14 +127,16 @@ private:
...
@@ -158,14 +127,16 @@ private:
return
fold
([](
auto
&
...
x
)
{
return
std
::
min
({
x
.
buf
.
size
()...});
});
return
fold
([](
auto
&
...
x
)
{
return
std
::
min
({
x
.
buf
.
size
()...});
});
}
}
// A zip
per
reached the end if any of its inputs reached the end.
// A zip reached the end if any of its inputs reached the end.
bool
at_end
()
{
bool
at_end
()
{
return
fold
([](
auto
&
...
inputs
)
{
return
(
inputs
.
at_end
()
||
...);
});
return
fold
([](
auto
&
...
inputs
)
{
return
(
inputs
.
at_end
()
||
...);
});
}
}
// -- callbacks for the forwarders -------------------------------------------
template
<
size_t
I
>
template
<
size_t
I
>
void
fwd_on_subscribe
(
zip
per
_index
<
I
>
index
,
subscription
sub
)
{
void
fwd_on_subscribe
(
zip_index
<
I
>
index
,
subscription
sub
)
{
if
(
!
term_
.
finalized
()
)
{
if
(
out_
)
{
auto
&
in
=
at
(
index
);
auto
&
in
=
at
(
index
);
if
(
!
in
.
sub
)
{
if
(
!
in
.
sub
)
{
if
(
demand_
>
0
)
if
(
demand_
>
0
)
...
@@ -180,49 +151,50 @@ private:
...
@@ -180,49 +151,50 @@ private:
}
}
template
<
size_t
I
>
template
<
size_t
I
>
void
fwd_on_complete
(
zip
per
_index
<
I
>
index
)
{
void
fwd_on_complete
(
zip_index
<
I
>
index
)
{
auto
&
input
=
at
(
index
);
if
(
out_
)
{
if
(
!
input
.
broken
)
{
auto
&
input
=
at
(
index
);
i
nput
.
broken
=
true
;
i
f
(
input
.
sub
)
input
.
sub
=
nullptr
;
input
.
sub
=
nullptr
;
if
(
input
.
buf
.
empty
())
if
(
input
.
buf
.
empty
())
fin
();
fin
();
}
}
}
}
template
<
size_t
I
>
template
<
size_t
I
>
void
fwd_on_error
(
zipper_index
<
I
>
index
,
const
error
&
what
)
{
void
fwd_on_error
(
zip_index
<
I
>
index
,
const
error
&
what
)
{
auto
&
input
=
at
(
index
);
if
(
out_
)
{
if
(
!
input
.
broken
)
{
auto
&
input
=
at
(
index
);
if
(
term_
.
active
()
&&
!
term_
.
err
())
if
(
input
.
sub
)
{
term_
.
err
(
what
);
if
(
!
err_
)
input
.
broken
=
true
;
err_
=
what
;
input
.
sub
=
nullptr
;
input
.
sub
=
nullptr
;
}
if
(
input
.
buf
.
empty
())
if
(
input
.
buf
.
empty
())
fin
();
fin
();
}
}
}
}
template
<
size_t
I
,
class
T
>
template
<
size_t
I
,
class
T
>
void
fwd_on_next
(
zip
per
_index
<
I
>
index
,
const
T
&
item
)
{
void
fwd_on_next
(
zip_index
<
I
>
index
,
const
T
&
item
)
{
if
(
term_
.
active
()
)
{
if
(
out_
)
{
at
(
index
).
buf
.
push_back
(
item
);
at
(
index
).
buf
.
push_back
(
item
);
push
();
push
();
}
}
}
}
private:
void
push
()
{
void
push
()
{
if
(
auto
n
=
std
::
min
(
buffered
(),
demand_
);
n
>
0
)
{
if
(
auto
n
=
std
::
min
(
buffered
(),
demand_
);
n
>
0
)
{
for
(
size_t
index
=
0
;
index
<
n
;
++
index
)
{
for
(
size_t
index
=
0
;
index
<
n
;
++
index
)
{
fold
([
this
,
index
](
auto
&
...
x
)
{
//
fold
([
this
,
index
](
auto
&
...
x
)
{
//
term
_
.
on_next
(
fn_
(
x
.
buf
[
index
]...));
out
_
.
on_next
(
fn_
(
x
.
buf
[
index
]...));
});
});
}
}
demand_
-=
n
;
demand_
-=
n
;
for_each_input
([
n
](
auto
,
auto
&
x
)
{
//
for_each_input
([
n
](
auto
,
auto
&
x
)
{
//
x
.
buf
.
erase
(
x
.
buf
.
begin
(),
x
.
buf
.
begin
()
+
n
);
x
.
buf
.
erase
(
x
.
buf
.
begin
(),
x
.
buf
.
begin
()
+
n
);
});
});
term_
.
push
();
}
}
if
(
at_end
())
if
(
at_end
())
fin
();
fin
();
...
@@ -230,50 +202,71 @@ private:
...
@@ -230,50 +202,71 @@ private:
void
fin
()
{
void
fin
()
{
for_each_input
([](
auto
,
auto
&
input
)
{
for_each_input
([](
auto
,
auto
&
input
)
{
input
.
in
=
nullptr
;
if
(
input
.
sub
)
{
if
(
input
.
sub
)
{
input
.
sub
.
cancel
();
input
.
sub
.
cancel
();
input
.
sub
=
nullptr
;
input
.
sub
=
nullptr
;
}
}
input
.
buf
.
clear
();
input
.
buf
.
clear
();
});
});
term_
.
fin
();
// Set out_ to null and emit the final event.
auto
out
=
std
::
move
(
out_
);
if
(
err_
)
out
.
on_error
(
err_
);
else
out
.
on_complete
();
}
}
size_t
demand_
=
0
;
/// Stores the context (coordinator) that runs this flow.
coordinator
*
ctx_
;
/// Reduces n inputs to 1 output.
F
fn_
;
F
fn_
;
std
::
tuple
<
zipper_input
<
Ts
>
...
>
inputs_
;
broadcast_step
<
output_type
>
term_
;
/// Stores the required state per input.
std
::
tuple
<
zip_input
<
Ts
>
...
>
inputs_
;
/// Stores our current demand for items from the subscriber.
size_t
demand_
=
0
;
/// Stores the first error that occurred on any input.
error
err_
;
/// Stores a handle to the subscribed observer.
observer
<
output_type
>
out_
;
};
};
/// Combines items from any number of observables using a zip function.
template
<
class
F
,
class
...
Ts
>
template
<
class
F
,
class
...
Ts
>
using
zipper_impl_ptr
=
intrusive_ptr
<
zipper_impl
<
F
,
Ts
...
>>
;
class
zip_with
:
public
cold
<
zip_with_output_t
<
F
,
Ts
...
>>
{
public:
/// @param fn The zip function. Takes one element from each input at a time and
// -- member types -----------------------------------------------------------
/// converts them into a single result.
/// @param input0 The input at index 0.
using
output_type
=
zip_with_output_t
<
F
,
Ts
...
>
;
/// @param input1 The input at index 1.
/// @param inputs The inputs for index > 1.
using
super
=
cold
<
output_type
>
;
template
<
class
F
,
class
T0
,
class
T1
,
class
...
Ts
>
auto
zip_with
(
F
fn
,
T0
input0
,
T1
input1
,
Ts
...
inputs
)
{
// -- constructors, destructors, and assignment operators --------------------
using
output_type
=
zipper_output_t
<
F
,
typename
T0
::
output_type
,
//
typename
T1
::
output_type
,
//
zip_with
(
coordinator
*
ctx
,
F
fn
,
observable
<
Ts
>
...
inputs
)
typename
Ts
::
output_type
...
>
;
:
super
(
ctx
),
fn_
(
std
::
move
(
fn
)),
inputs_
(
inputs
...)
{
using
impl_t
=
zipper_impl
<
F
,
//
// nop
typename
T0
::
output_type
,
//
}
typename
T1
::
output_type
,
//
typename
Ts
::
output_type
...
>
;
// -- implementation of observable<T>::impl ----------------------------------
if
(
input0
.
valid
()
&&
input1
.
valid
()
&&
(
inputs
.
valid
()
&&
...))
{
auto
ctx
=
input0
.
ctx
();
disposable
subscribe
(
observer
<
output_type
>
out
)
override
{
auto
ptr
=
make_counted
<
impl_t
>
(
ctx
,
std
::
move
(
fn
),
auto
ptr
=
make_counted
<
zip_with_sub
<
F
,
Ts
...
>>
(
super
::
ctx_
,
fn_
,
out
,
std
::
move
(
input0
).
as_observable
(),
inputs_
);
std
::
move
(
input1
).
as_observable
(),
out
.
on_subscribe
(
subscription
{
ptr
});
std
::
move
(
inputs
).
as_observable
()...);
return
ptr
->
as_disposable
();
return
observable
<
output_type
>
{
std
::
move
(
ptr
)};
}
else
{
return
observable
<
output_type
>
{};
}
}
}
}
// namespace caf::flow
private:
/// Reduces n inputs to 1 output.
F
fn_
;
/// Stores the source observables until an observer subscribes.
std
::
tuple
<
observable
<
Ts
>
...
>
inputs_
;
};
}
// namespace caf::flow::op
libcaf_core/caf/flow/single.hpp
View file @
04e02412
...
@@ -12,7 +12,6 @@
...
@@ -12,7 +12,6 @@
#include "caf/disposable.hpp"
#include "caf/disposable.hpp"
#include "caf/error.hpp"
#include "caf/error.hpp"
#include "caf/flow/observable.hpp"
#include "caf/flow/observable.hpp"
#include "caf/none.hpp"
#include "caf/ref_counted.hpp"
#include "caf/ref_counted.hpp"
namespace
caf
::
flow
{
namespace
caf
::
flow
{
...
@@ -24,94 +23,7 @@ class single {
...
@@ -24,94 +23,7 @@ class single {
public:
public:
using
output_type
=
T
;
using
output_type
=
T
;
/// Internal interface of a `single`.
explicit
single
(
intrusive_ptr
<
op
::
base
<
T
>>
pimpl
)
noexcept
class
impl
:
public
observable_impl_base
<
T
>
{
public:
using
super
=
observable_impl_base
<
T
>
;
CAF_INTRUSIVE_PTR_FRIENDS
(
impl
)
explicit
impl
(
coordinator
*
ctx
)
:
super
(
ctx
)
{
// nop
}
disposable
subscribe
(
observer
<
T
>
what
)
override
{
if
(
!
std
::
holds_alternative
<
error
>
(
value_
))
{
auto
res
=
super
::
do_subscribe
(
what
.
ptr
());
observers_
.
emplace_back
(
std
::
move
(
what
),
0u
);
return
res
;
}
else
{
what
.
on_error
(
std
::
get
<
error
>
(
value_
));
return
disposable
{};
}
}
void
on_request
(
disposable_impl
*
sink
,
size_t
n
)
override
{
auto
pred
=
[
sink
](
auto
&
entry
)
{
return
entry
.
first
.
ptr
()
==
sink
;
};
if
(
auto
i
=
std
::
find_if
(
observers_
.
begin
(),
observers_
.
end
(),
pred
);
i
!=
observers_
.
end
())
{
auto
f
=
detail
::
make_overload
(
//
[
i
,
n
](
none_t
)
{
i
->
second
+=
n
;
},
[
this
,
i
](
const
T
&
val
)
{
i
->
first
.
on_next
(
val
);
i
->
first
.
on_complete
();
observers_
.
erase
(
i
);
},
[
this
,
i
](
const
error
&
err
)
{
i
->
first
.
on_error
(
err
);
observers_
.
erase
(
i
);
});
std
::
visit
(
f
,
value_
);
}
}
void
on_cancel
(
disposable_impl
*
sink
)
override
{
auto
pred
=
[
sink
](
auto
&
entry
)
{
return
entry
.
first
.
ptr
()
==
sink
;
};
if
(
auto
i
=
std
::
find_if
(
observers_
.
begin
(),
observers_
.
end
(),
pred
);
i
!=
observers_
.
end
())
observers_
.
erase
(
i
);
}
void
dispose
()
override
{
if
(
!
std
::
holds_alternative
<
error
>
(
value_
))
set_error
(
make_error
(
sec
::
disposed
));
}
bool
disposed
()
const
noexcept
override
{
return
observers_
.
empty
()
&&
!
std
::
holds_alternative
<
none_t
>
(
value_
);
}
void
set_value
(
T
val
)
{
if
(
std
::
holds_alternative
<
none_t
>
(
value_
))
{
value_
=
std
::
move
(
val
);
auto
&
ref
=
std
::
get
<
T
>
(
value_
);
auto
pred
=
[](
auto
&
entry
)
{
return
entry
.
second
==
0
;
};
if
(
auto
first
=
std
::
partition
(
observers_
.
begin
(),
observers_
.
end
(),
pred
);
first
!=
observers_
.
end
())
{
for
(
auto
i
=
first
;
i
!=
observers_
.
end
();
++
i
)
{
i
->
first
.
on_next
(
ref
);
i
->
first
.
on_complete
();
}
observers_
.
erase
(
first
,
observers_
.
end
());
}
}
}
void
set_error
(
error
err
)
{
value_
=
std
::
move
(
err
);
auto
&
ref
=
std
::
get
<
error
>
(
value_
);
for
(
auto
&
entry
:
observers_
)
entry
.
first
.
on_error
(
ref
);
observers_
.
clear
();
}
private:
std
::
variant
<
none_t
,
T
,
error
>
value_
;
std
::
vector
<
std
::
pair
<
observer
<
T
>
,
size_t
>>
observers_
;
};
explicit
single
(
intrusive_ptr
<
impl
>
pimpl
)
noexcept
:
pimpl_
(
std
::
move
(
pimpl
))
{
:
pimpl_
(
std
::
move
(
pimpl
))
{
// nop
// nop
}
}
...
@@ -127,13 +39,6 @@ public:
...
@@ -127,13 +39,6 @@ public:
single
&
operator
=
(
single
&&
)
noexcept
=
default
;
single
&
operator
=
(
single
&&
)
noexcept
=
default
;
single
&
operator
=
(
const
single
&
)
noexcept
=
default
;
single
&
operator
=
(
const
single
&
)
noexcept
=
default
;
disposable
as_disposable
()
&&
{
return
disposable
{
std
::
move
(
pimpl_
)};
}
disposable
as_disposable
()
const
&
{
return
disposable
{
pimpl_
};
}
observable
<
T
>
as_observable
()
&&
{
observable
<
T
>
as_observable
()
&&
{
return
observable
<
T
>
{
std
::
move
(
pimpl_
)};
return
observable
<
T
>
{
std
::
move
(
pimpl_
)};
}
}
...
@@ -156,16 +61,6 @@ public:
...
@@ -156,16 +61,6 @@ public:
std
::
move
(
on_error
));
std
::
move
(
on_error
));
}
}
void
set_value
(
T
val
)
{
if
(
pimpl_
)
pimpl_
->
set_value
(
std
::
move
(
val
));
}
void
set_error
(
error
err
)
{
if
(
pimpl_
)
pimpl_
->
set_error
(
std
::
move
(
err
));
}
bool
valid
()
const
noexcept
{
bool
valid
()
const
noexcept
{
return
pimpl_
!=
nullptr
;
return
pimpl_
!=
nullptr
;
}
}
...
@@ -178,36 +73,23 @@ public:
...
@@ -178,36 +73,23 @@ public:
return
!
valid
();
return
!
valid
();
}
}
impl
*
ptr
()
{
return
pimpl_
.
get
();
}
const
impl
*
ptr
()
const
{
return
pimpl_
.
get
();
}
const
intrusive_ptr
<
impl
>&
as_intrusive_ptr
()
const
&
noexcept
{
return
pimpl_
;
}
intrusive_ptr
<
impl
>&&
as_intrusive_ptr
()
&&
noexcept
{
return
std
::
move
(
pimpl_
);
}
void
swap
(
single
&
other
)
{
void
swap
(
single
&
other
)
{
pimpl_
.
swap
(
other
.
pimpl_
);
pimpl_
.
swap
(
other
.
pimpl_
);
}
}
private:
private:
intrusive_ptr
<
impl
>
pimpl_
;
intrusive_ptr
<
op
::
base
<
T
>
>
pimpl_
;
};
};
template
<
class
T
>
/// Convenience function for creating an @ref observable from a concrete
using
single_impl
=
typename
single
<
T
>::
impl
;
/// operator type.
template
<
class
Operator
,
class
...
Ts
>
template
<
class
T
>
single
<
typename
Operator
::
output_type
>
single
<
T
>
make_single
(
coordinator
*
ctx
)
{
make_single
(
coordinator
*
ctx
,
Ts
&&
...
xs
)
{
return
single
<
T
>
{
make_counted
<
single_impl
<
T
>>
(
ctx
)};
using
out_t
=
typename
Operator
::
output_type
;
using
ptr_t
=
intrusive_ptr
<
out_t
>
;
ptr_t
ptr
{
new
Operator
(
ctx
,
std
::
forward
<
Ts
>
(
xs
)...),
false
};
return
single
<
out_t
>
{
std
::
move
(
ptr
)};
}
}
}
// namespace caf::flow
}
// namespace caf::flow
libcaf_core/caf/flow/step.hpp
View file @
04e02412
...
@@ -12,7 +12,9 @@
...
@@ -12,7 +12,9 @@
#include "caf/flow/observer.hpp"
#include "caf/flow/observer.hpp"
#include <algorithm>
#include <algorithm>
#include <numeric>
#include <type_traits>
#include <type_traits>
#include <unordered_set>
namespace
caf
::
flow
{
namespace
caf
::
flow
{
...
@@ -107,6 +109,70 @@ struct filter_step {
...
@@ -107,6 +109,70 @@ struct filter_step {
}
}
};
};
template
<
class
Predicate
>
struct
take_while_step
{
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
;
Predicate
predicate
;
template
<
class
Next
,
class
...
Steps
>
bool
on_next
(
const
input_type
&
item
,
Next
&
next
,
Steps
&
...
steps
)
{
if
(
predicate
(
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
...);
}
};
template
<
class
T
>
struct
distinct_step
{
using
input_type
=
T
;
using
output_type
=
T
;
std
::
unordered_set
<
T
>
prev
;
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
...);
}
};
template
<
class
Fn
>
template
<
class
Fn
>
struct
map_step
{
struct
map_step
{
using
trait
=
detail
::
get_callable_trait_t
<
Fn
>
;
using
trait
=
detail
::
get_callable_trait_t
<
Fn
>
;
...
@@ -139,6 +205,44 @@ struct map_step {
...
@@ -139,6 +205,44 @@ struct map_step {
}
}
};
};
template
<
class
T
,
class
Reducer
>
struct
reduce_step
{
T
result
;
Reducer
fn
;
reduce_step
(
T
init
,
Reducer
reducer
)
:
result
(
std
::
move
(
init
)),
fn
(
reducer
)
{
// nop
}
reduce_step
(
reduce_step
&&
)
=
default
;
reduce_step
(
const
reduce_step
&
)
=
default
;
reduce_step
&
operator
=
(
reduce_step
&&
)
=
default
;
reduce_step
&
operator
=
(
const
reduce_step
&
)
=
default
;
using
input_type
=
T
;
using
output_type
=
T
;
template
<
class
Next
,
class
...
Steps
>
bool
on_next
(
const
input_type
&
item
,
Next
&
,
Steps
&
...)
{
result
=
fn
(
std
::
move
(
result
),
item
);
return
true
;
}
template
<
class
Next
,
class
...
Steps
>
void
on_complete
(
Next
&
next
,
Steps
&
...
steps
)
{
if
(
next
.
on_next
(
result
,
steps
...))
next
.
on_complete
(
steps
...);
}
template
<
class
Next
,
class
...
Steps
>
void
on_error
(
const
error
&
what
,
Next
&
next
,
Steps
&
...
steps
)
{
if
(
next
.
on_next
(
result
,
steps
...))
next
.
on_error
(
what
,
steps
...);
}
};
template
<
class
Fn
>
template
<
class
Fn
>
struct
flat_map_optional_step
{
struct
flat_map_optional_step
{
using
trait
=
detail
::
get_callable_trait_t
<
Fn
>
;
using
trait
=
detail
::
get_callable_trait_t
<
Fn
>
;
...
@@ -177,7 +281,7 @@ struct flat_map_optional_step {
...
@@ -177,7 +281,7 @@ struct flat_map_optional_step {
};
};
template
<
class
T
,
class
Fn
>
template
<
class
T
,
class
Fn
>
struct
do_on_
complete
_step
{
struct
do_on_
next
_step
{
using
input_type
=
T
;
using
input_type
=
T
;
using
output_type
=
T
;
using
output_type
=
T
;
...
@@ -186,12 +290,12 @@ struct do_on_complete_step {
...
@@ -186,12 +290,12 @@ struct do_on_complete_step {
template
<
class
Next
,
class
...
Steps
>
template
<
class
Next
,
class
...
Steps
>
bool
on_next
(
const
input_type
&
item
,
Next
&
next
,
Steps
&
...
steps
)
{
bool
on_next
(
const
input_type
&
item
,
Next
&
next
,
Steps
&
...
steps
)
{
fn
(
item
);
return
next
.
on_next
(
item
,
steps
...);
return
next
.
on_next
(
item
,
steps
...);
}
}
template
<
class
Next
,
class
...
Steps
>
template
<
class
Next
,
class
...
Steps
>
void
on_complete
(
Next
&
next
,
Steps
&
...
steps
)
{
void
on_complete
(
Next
&
next
,
Steps
&
...
steps
)
{
fn
();
next
.
on_complete
(
steps
...);
next
.
on_complete
(
steps
...);
}
}
...
@@ -202,7 +306,7 @@ struct do_on_complete_step {
...
@@ -202,7 +306,7 @@ struct do_on_complete_step {
};
};
template
<
class
T
,
class
Fn
>
template
<
class
T
,
class
Fn
>
struct
do_on_
error
_step
{
struct
do_on_
complete
_step
{
using
input_type
=
T
;
using
input_type
=
T
;
using
output_type
=
T
;
using
output_type
=
T
;
...
@@ -216,18 +320,18 @@ struct do_on_error_step {
...
@@ -216,18 +320,18 @@ struct do_on_error_step {
template
<
class
Next
,
class
...
Steps
>
template
<
class
Next
,
class
...
Steps
>
void
on_complete
(
Next
&
next
,
Steps
&
...
steps
)
{
void
on_complete
(
Next
&
next
,
Steps
&
...
steps
)
{
fn
();
next
.
on_complete
(
steps
...);
next
.
on_complete
(
steps
...);
}
}
template
<
class
Next
,
class
...
Steps
>
template
<
class
Next
,
class
...
Steps
>
void
on_error
(
const
error
&
what
,
Next
&
next
,
Steps
&
...
steps
)
{
void
on_error
(
const
error
&
what
,
Next
&
next
,
Steps
&
...
steps
)
{
fn
(
what
);
next
.
on_error
(
what
,
steps
...);
next
.
on_error
(
what
,
steps
...);
}
}
};
};
template
<
class
T
,
class
Fn
>
template
<
class
T
,
class
Fn
>
struct
do_
finally
_step
{
struct
do_
on_error
_step
{
using
input_type
=
T
;
using
input_type
=
T
;
using
output_type
=
T
;
using
output_type
=
T
;
...
@@ -241,24 +345,24 @@ struct do_finally_step {
...
@@ -241,24 +345,24 @@ struct do_finally_step {
template
<
class
Next
,
class
...
Steps
>
template
<
class
Next
,
class
...
Steps
>
void
on_complete
(
Next
&
next
,
Steps
&
...
steps
)
{
void
on_complete
(
Next
&
next
,
Steps
&
...
steps
)
{
fn
();
next
.
on_complete
(
steps
...);
next
.
on_complete
(
steps
...);
}
}
template
<
class
Next
,
class
...
Steps
>
template
<
class
Next
,
class
...
Steps
>
void
on_error
(
const
error
&
what
,
Next
&
next
,
Steps
&
...
steps
)
{
void
on_error
(
const
error
&
what
,
Next
&
next
,
Steps
&
...
steps
)
{
fn
();
fn
(
what
);
next
.
on_error
(
what
,
steps
...);
next
.
on_error
(
what
,
steps
...);
}
}
};
};
/// Catches errors by converting them into complete events instead.
template
<
class
T
,
class
Fn
>
template
<
class
T
>
struct
do_finally_step
{
struct
on_error_complete_step
{
using
input_type
=
T
;
using
input_type
=
T
;
using
output_type
=
T
;
using
output_type
=
T
;
Fn
fn
;
template
<
class
Next
,
class
...
Steps
>
template
<
class
Next
,
class
...
Steps
>
bool
on_next
(
const
input_type
&
item
,
Next
&
next
,
Steps
&
...
steps
)
{
bool
on_next
(
const
input_type
&
item
,
Next
&
next
,
Steps
&
...
steps
)
{
return
next
.
on_next
(
item
,
steps
...);
return
next
.
on_next
(
item
,
steps
...);
...
@@ -266,331 +370,38 @@ struct on_error_complete_step {
...
@@ -266,331 +370,38 @@ struct on_error_complete_step {
template
<
class
Next
,
class
...
Steps
>
template
<
class
Next
,
class
...
Steps
>
void
on_complete
(
Next
&
next
,
Steps
&
...
steps
)
{
void
on_complete
(
Next
&
next
,
Steps
&
...
steps
)
{
fn
();
next
.
on_complete
(
steps
...);
next
.
on_complete
(
steps
...);
}
}
template
<
class
Next
,
class
...
Steps
>
template
<
class
Next
,
class
...
Steps
>
void
on_error
(
const
error
&
,
Next
&
next
,
Steps
&
...
steps
)
{
void
on_error
(
const
error
&
what
,
Next
&
next
,
Steps
&
...
steps
)
{
next
.
on_complete
(
steps
...);
fn
();
next
.
on_error
(
what
,
steps
...);
}
}
};
};
/// Wraps logic for pushing data to multiple observers with broadcast semantics,
/// Catches errors by converting them into `complete` events instead.
/// i.e., all observers see the same items at the same time and the flow adjusts
/// to the slowest observer. This step may only be used as terminal step.
template
<
class
T
>
template
<
class
T
>
class
broadcast_step
{
struct
on_error_complete_step
{
public:
using
input_type
=
T
;
// -- member types -----------------------------------------------------------
using
output_type
=
T
;
using
output_type
=
T
;
using
observer_impl_t
=
observer_impl
<
output_type
>
;
template
<
class
Next
,
class
...
Steps
>
bool
on_next
(
const
input_type
&
item
,
Next
&
next
,
Steps
&
...
steps
)
{
struct
output_t
{
return
next
.
on_next
(
item
,
steps
...);
size_t
demand
;
observer
<
output_type
>
sink
;
};
// -- constructors, destructors, and assignment operators --------------------
broadcast_step
()
{
// Reserve some buffer space in order to avoid frequent re-allocations while
// warming up.
buf_
.
reserve
(
32
);
}
// -- properties -------------------------------------------------------------
size_t
min_demand
()
const
noexcept
{
if
(
!
outputs_
.
empty
())
{
auto
i
=
outputs_
.
begin
();
auto
init
=
(
*
i
++
).
demand
;
return
std
::
accumulate
(
i
,
outputs_
.
end
(),
init
,
[](
size_t
x
,
const
output_t
&
y
)
{
return
std
::
min
(
x
,
y
.
demand
);
});
}
else
{
return
0
;
}
}
size_t
max_demand
()
const
noexcept
{
if
(
!
outputs_
.
empty
())
{
auto
i
=
outputs_
.
begin
();
auto
init
=
(
*
i
++
).
demand
;
return
std
::
accumulate
(
i
,
outputs_
.
end
(),
init
,
[](
size_t
x
,
const
output_t
&
y
)
{
return
std
::
max
(
x
,
y
.
demand
);
});
}
else
{
return
0
;
}
}
/// Returns how many items are currently buffered at this step.
size_t
buffered
()
const
noexcept
{
return
buf_
.
size
();
}
/// Returns the number of current observers.
size_t
num_observers
()
const
noexcept
{
return
outputs_
.
size
();
}
/// Convenience function for calling `is_active(state())`;
bool
active
()
const
noexcept
{
return
is_active
(
state_
);
}
/// Queries whether the current state is `observable_state::completing`.
bool
completing
()
const
noexcept
{
return
state_
==
observable_state
::
completing
;
}
/// Convenience function for calling `is_final(state())`;
bool
finalized
()
const
noexcept
{
return
is_final
(
state_
);
}
/// Returns the current state.
observable_state
state
()
const
noexcept
{
return
state_
;
}
const
error
&
err
()
const
noexcept
{
return
err_
;
}
void
err
(
error
x
)
{
err_
=
std
::
move
(
x
);
}
// -- demand management ------------------------------------------------------
size_t
next_demand
()
{
auto
have
=
buf_
.
size
()
+
in_flight_
;
auto
want
=
max_demand
();
if
(
want
>
have
)
{
auto
delta
=
want
-
have
;
in_flight_
+=
delta
;
return
delta
;
}
else
{
return
0
;
}
}
// -- callbacks for the parent -----------------------------------------------
/// Tries to add a new observer.
bool
add
(
observer
<
output_type
>
sink
)
{
if
(
is_active
(
state_
))
{
outputs_
.
emplace_back
(
output_t
{
0
,
std
::
move
(
sink
)});
return
true
;
}
else
if
(
err_
)
{
sink
.
on_error
(
err_
);
return
false
;
}
else
{
sink
.
on_error
(
make_error
(
sec
::
disposed
));
return
false
;
}
}
/// Tries to add a new observer and returns `parent->do_subscribe(sink)` on
/// success or a default-constructed @ref disposable otherwise.
template
<
class
Parent
>
disposable
add
(
Parent
*
parent
,
observer
<
output_type
>
sink
)
{
if
(
add
(
sink
))
{
return
parent
->
do_subscribe
(
sink
);
}
else
{
return
disposable
{};
}
}
/// Requests `n` more items for `sink`.
/// @returns New demand to signal upstream or 0.
/// @note Calls @ref push.
size_t
on_request
(
disposable_impl
*
sink
,
size_t
n
)
{
if
(
auto
i
=
find
(
sink
);
i
!=
outputs_
.
end
())
{
i
->
demand
+=
n
;
push
();
return
next_demand
();
}
else
{
return
0
;
}
}
/// Requests `n` more items for `sink`.
/// @note Calls @ref push and may call `sub.request(n)`.
void
on_request
(
subscription
&
sub
,
disposable_impl
*
sink
,
size_t
n
)
{
if
(
auto
new_demand
=
on_request
(
sink
,
n
);
new_demand
>
0
&&
sub
)
sub
.
request
(
new_demand
);
}
/// Removes `sink` from the observer set.
/// @returns New demand to signal upstream or 0.
/// @note Calls @ref push.
size_t
on_cancel
(
disposable_impl
*
sink
)
{
if
(
auto
i
=
find
(
sink
);
i
!=
outputs_
.
end
())
{
outputs_
.
erase
(
i
);
// TODO: shut down on last cancel?
push
();
return
next_demand
();
}
else
{
return
0
;
}
}
/// Requests `n` more items for `sink`.
/// @note Calls @ref push and may call `sub.request(n)`.
void
on_cancel
(
subscription
&
sub
,
disposable_impl
*
sink
)
{
if
(
auto
new_demand
=
on_cancel
(
sink
);
new_demand
>
0
&&
sub
)
sub
.
request
(
new_demand
);
}
/// Tries to deliver items from the buffer to the observers.
void
push
()
{
// Must not be re-entered. Any on_request call must use the event loop.
CAF_ASSERT
(
!
pushing_
);
CAF_DEBUG_STMT
(
pushing_
=
true
);
// Sanity checking.
if
(
outputs_
.
empty
())
return
;
// Push data downstream and adjust demand on each path.
if
(
auto
n
=
std
::
min
(
min_demand
(),
buf_
.
size
());
n
>
0
)
{
auto
items
=
span
<
output_type
>
{
buf_
.
data
(),
n
};
for
(
auto
&
out
:
outputs_
)
{
out
.
demand
-=
n
;
for
(
auto
&
item
:
items
)
out
.
sink
.
on_next
(
item
);
}
buf_
.
erase
(
buf_
.
begin
(),
buf_
.
begin
()
+
n
);
}
if
(
state_
==
observable_state
::
completing
&&
buf_
.
empty
())
{
if
(
!
err_
)
{
for
(
auto
&
out
:
outputs_
)
out
.
sink
.
on_complete
();
state_
=
observable_state
::
completed
;
}
else
{
for
(
auto
&
out
:
outputs_
)
out
.
sink
.
on_error
(
err_
);
state_
=
observable_state
::
aborted
;
}
}
CAF_DEBUG_STMT
(
pushing_
=
false
);
}
/// Checks whether the broadcaster currently has no pending data.
bool
idle
()
{
return
buf_
.
empty
();
}
/// Calls `on_complete` on all observers and drops any pending data.
void
close
()
{
buf_
.
clear
();
if
(
!
err_
)
{
for
(
auto
&
out
:
outputs_
)
out
.
sink
.
on_complete
();
state_
=
observable_state
::
completed
;
}
else
{
for
(
auto
&
out
:
outputs_
)
out
.
sink
.
on_error
(
err_
);
state_
=
observable_state
::
aborted
;
}
outputs_
.
clear
();
}
/// Calls `on_error` on all observers and drops any pending data.
void
abort
(
const
error
&
reason
)
{
err_
=
reason
;
close
();
}
// -- callbacks for steps ----------------------------------------------------
bool
on_next
(
const
output_type
&
item
)
{
// Note: we may receive more data than what we have requested.
if
(
in_flight_
>
0
)
--
in_flight_
;
buf_
.
emplace_back
(
item
);
return
true
;
}
void
fin
()
{
if
(
is_active
(
state_
))
{
if
(
idle
())
{
close
();
}
else
{
state_
=
observable_state
::
completing
;
}
}
}
void
on_complete
()
{
fin
();
}
void
on_error
(
const
error
&
what
)
{
err_
=
what
;
fin
();
}
// -- callbacks for the parent -----------------------------------------------
void
dispose
()
{
on_complete
();
}
/// Tries to set the state from `idle` to `running`.
bool
start
()
{
if
(
state_
==
observable_state
::
idle
)
{
state_
=
observable_state
::
running
;
return
true
;
}
else
{
return
false
;
}
}
}
/// Tries to set the state from `idle` to `running`. On success, requests
template
<
class
Next
,
class
...
Steps
>
/// items on `sub` if there is already demand. Calls `sub.cancel()` when
void
on_complete
(
Next
&
next
,
Steps
&
...
steps
)
{
/// returning `false`.
next
.
on_complete
(
steps
...);
bool
start
(
subscription
&
sub
)
{
if
(
start
())
{
if
(
auto
n
=
next_demand
();
n
>
0
)
sub
.
request
(
n
);
return
true
;
}
else
{
sub
.
cancel
();
return
false
;
}
}
}
private:
template
<
class
Next
,
class
...
Steps
>
typename
std
::
vector
<
output_t
>::
iterator
find
(
disposable_impl
*
sink
)
{
void
on_error
(
const
error
&
,
Next
&
next
,
Steps
&
...
steps
)
{
auto
e
=
outputs_
.
end
();
next
.
on_complete
(
steps
...);
auto
pred
=
[
sink
](
const
output_t
&
out
)
{
return
out
.
sink
.
ptr
()
==
sink
;
};
return
std
::
find_if
(
outputs_
.
begin
(),
e
,
pred
);
}
}
/// Buffers outbound items until we can ship them.
std
::
vector
<
output_type
>
buf_
;
/// Keeps track of how many items have been requested but did not arrive yet.
size_t
in_flight_
=
0
;
/// Stores handles to the observer plus their demand.
std
::
vector
<
output_t
>
outputs_
;
/// Keeps track of our current state.
observable_state
state_
=
observable_state
::
idle
;
/// Stores the on_error argument.
error
err_
;
#ifdef CAF_ENABLE_RUNTIME_CHECKS
/// Protect against re-entering `push`.
bool
pushing_
=
false
;
#endif
};
};
/// Utility for the observables that use one or more steps.
/// Utility for the observables that use one or more steps.
...
...
libcaf_core/caf/flow/subscription.hpp
View file @
04e02412
...
@@ -5,7 +5,9 @@
...
@@ -5,7 +5,9 @@
#pragma once
#pragma once
#include "caf/detail/core_export.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/detail/ref_counted_base.hpp"
#include "caf/disposable.hpp"
#include "caf/disposable.hpp"
#include "caf/flow/coordinated.hpp"
#include "caf/flow/fwd.hpp"
#include "caf/flow/fwd.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/ref_counted.hpp"
#include "caf/ref_counted.hpp"
...
@@ -35,55 +37,38 @@ public:
...
@@ -35,55 +37,38 @@ public:
void
dispose
()
final
;
void
dispose
()
final
;
};
};
/// A trivial subscription type that drops all member function calls.
/// Simple base type for all subscription implementations that implements the
class
CAF_CORE_EXPORT
nop_impl
final
:
public
ref_counted
,
public
impl
{
/// reference counting member functions.
class
CAF_CORE_EXPORT
impl_base
:
public
detail
::
ref_counted_base
,
public
impl
{
public:
public:
// -- friends --------------------------------------------------------------
void
ref_disposable
()
const
noexcept
final
;
CAF_INTRUSIVE_PTR_FRIENDS
(
nop_impl
)
void
deref_disposable
()
const
noexcept
final
;
bool
disposed
()
const
noexcept
override
;
void
ref_disposable
()
const
noexcept
override
;
void
deref_disposable
()
const
noexcept
override
;
void
cancel
()
override
;
void
request
(
size_t
n
)
override
;
private:
bool
disposed_
=
false
;
};
};
/// Describes a listener to the subscription that will receive an event
/// Describes a listener to the subscription that will receive an event
/// whenever the observer calls `request` or `cancel`.
/// whenever the observer calls `request` or `cancel`.
class
CAF_CORE_EXPORT
listener
:
public
disposable
::
impl
{
class
CAF_CORE_EXPORT
listener
:
public
coordinated
{
public:
public:
virtual
~
listener
();
virtual
~
listener
();
virtual
void
on_request
(
disposable
::
impl
*
sink
,
size_t
n
)
=
0
;
virtual
void
on_request
(
coordinated
*
sink
,
size_t
n
)
=
0
;
virtual
void
on_cancel
(
disposable
::
impl
*
sink
)
=
0
;
virtual
void
on_cancel
(
coordinated
*
sink
)
=
0
;
};
};
/// Default implementation for subscriptions that forward `request` and
/// Default implementation for subscriptions that forward `request` and
/// `cancel` to a @ref listener.
/// `cancel` to a @ref listener.
class
CAF_CORE_EXPORT
default_impl
final
:
public
ref_counted
,
public
impl
{
class
CAF_CORE_EXPORT
fwd_impl
final
:
public
impl_base
{
public:
public:
CAF_INTRUSIVE_PTR_FRIENDS
(
default_impl
)
fwd_impl
(
coordinator
*
ctx
,
listener
*
src
,
coordinated
*
snk
)
default_impl
(
coordinator
*
ctx
,
listener
*
src
,
disposable
::
impl
*
snk
)
:
ctx_
(
ctx
),
src_
(
src
),
snk_
(
snk
)
{
:
ctx_
(
ctx
),
src_
(
src
),
snk_
(
snk
)
{
// nop
// nop
}
}
bool
disposed
()
const
noexcept
override
;
bool
disposed
()
const
noexcept
override
;
void
ref_disposable
()
const
noexcept
override
;
void
deref_disposable
()
const
noexcept
override
;
void
request
(
size_t
n
)
override
;
void
request
(
size_t
n
)
override
;
void
cancel
()
override
;
void
cancel
()
override
;
...
@@ -96,28 +81,28 @@ public:
...
@@ -96,28 +81,28 @@ public:
/// @param ctx The owner of @p src and @p snk.
/// @param ctx The owner of @p src and @p snk.
/// @param src The @ref observable that emits items.
/// @param src The @ref observable that emits items.
/// @param snk the @ref observer that consumes items.
/// @param snk the @ref observer that consumes items.
/// @returns an instance of @ref
default
_impl in a @ref subscription handle.
/// @returns an instance of @ref
fwd
_impl in a @ref subscription handle.
template
<
class
Observable
,
class
Observer
>
template
<
class
Observable
,
class
Observer
>
static
subscription
make
(
coordinator
*
ctx
,
Observable
*
src
,
Observer
*
snk
)
{
static
subscription
make
(
coordinator
*
ctx
,
Observable
*
src
,
Observer
*
snk
)
{
static_assert
(
std
::
is_base_of_v
<
listener
,
Observable
>
);
static_assert
(
std
::
is_base_of_v
<
listener
,
Observable
>
);
static_assert
(
std
::
is_base_of_v
<
disposable_impl
,
Observer
>
);
static_assert
(
std
::
is_base_of_v
<
coordinated
,
Observer
>
);
static_assert
(
std
::
is_same_v
<
typename
Observable
::
output_type
,
static_assert
(
std
::
is_same_v
<
typename
Observable
::
output_type
,
typename
Observer
::
input_type
>
);
typename
Observer
::
input_type
>
);
intrusive_ptr
<
impl
>
ptr
{
new
default
_impl
(
ctx
,
src
,
snk
),
false
};
intrusive_ptr
<
impl
>
ptr
{
new
fwd
_impl
(
ctx
,
src
,
snk
),
false
};
return
subscription
{
std
::
move
(
ptr
)};
return
subscription
{
std
::
move
(
ptr
)};
}
}
/// Like @ref make but without any type checking.
/// Like @ref make but without any type checking.
static
subscription
make_unsafe
(
coordinator
*
ctx
,
listener
*
src
,
static
subscription
make_unsafe
(
coordinator
*
ctx
,
listener
*
src
,
disposable_impl
*
snk
)
{
coordinated
*
snk
)
{
intrusive_ptr
<
impl
>
ptr
{
new
default
_impl
(
ctx
,
src
,
snk
),
false
};
intrusive_ptr
<
impl
>
ptr
{
new
fwd
_impl
(
ctx
,
src
,
snk
),
false
};
return
subscription
{
std
::
move
(
ptr
)};
return
subscription
{
std
::
move
(
ptr
)};
}
}
private:
private:
coordinator
*
ctx_
;
coordinator
*
ctx_
;
intrusive_ptr
<
listener
>
src_
;
intrusive_ptr
<
listener
>
src_
;
intrusive_ptr
<
disposable_impl
>
snk_
;
intrusive_ptr
<
coordinated
>
snk_
;
};
};
// -- constructors, destructors, and assignment operators --------------------
// -- constructors, destructors, and assignment operators --------------------
...
@@ -202,4 +187,7 @@ private:
...
@@ -202,4 +187,7 @@ private:
intrusive_ptr
<
impl
>
pimpl_
;
intrusive_ptr
<
impl
>
pimpl_
;
};
};
/// @ref subscription
using
subscription_impl
=
subscription
::
impl
;
}
// namespace caf::flow
}
// namespace caf::flow
libcaf_core/caf/ref_counted.hpp
View file @
04e02412
...
@@ -8,6 +8,7 @@
...
@@ -8,6 +8,7 @@
#include <cstddef>
#include <cstddef>
#include "caf/detail/core_export.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/detail/ref_counted_base.hpp"
namespace
caf
{
namespace
caf
{
...
@@ -15,31 +16,13 @@ namespace caf {
...
@@ -15,31 +16,13 @@ namespace caf {
/// Serves the requirements of {@link intrusive_ptr}.
/// Serves the requirements of {@link intrusive_ptr}.
/// @note *All* instances of `ref_counted` start with a reference count of 1.
/// @note *All* instances of `ref_counted` start with a reference count of 1.
/// @relates intrusive_ptr
/// @relates intrusive_ptr
class
CAF_CORE_EXPORT
ref_counted
{
class
CAF_CORE_EXPORT
ref_counted
:
public
detail
::
ref_counted_base
{
public:
public:
virtual
~
ref_counted
()
;
using
super
=
ref_counted_base
;
ref_counted
();
using
super
::
super
;
ref_counted
(
const
ref_counted
&
);
ref_counted
&
operator
=
(
const
ref_counted
&
);
/// Increases reference count by one.
~
ref_counted
()
override
;
void
ref
()
const
noexcept
{
rc_
.
fetch_add
(
1
,
std
::
memory_order_relaxed
);
}
/// Decreases reference count by one and calls `request_deletion`
/// when it drops to zero.
void
deref
()
const
noexcept
;
/// Queries whether there is exactly one reference.
bool
unique
()
const
noexcept
{
return
rc_
==
1
;
}
size_t
get_reference_count
()
const
noexcept
{
return
rc_
;
}
friend
void
intrusive_ptr_add_ref
(
const
ref_counted
*
p
)
noexcept
{
friend
void
intrusive_ptr_add_ref
(
const
ref_counted
*
p
)
noexcept
{
p
->
ref
();
p
->
ref
();
...
@@ -48,9 +31,6 @@ public:
...
@@ -48,9 +31,6 @@ public:
friend
void
intrusive_ptr_release
(
const
ref_counted
*
p
)
noexcept
{
friend
void
intrusive_ptr_release
(
const
ref_counted
*
p
)
noexcept
{
p
->
deref
();
p
->
deref
();
}
}
protected:
mutable
std
::
atomic
<
size_t
>
rc_
;
};
};
}
// namespace caf
}
// namespace caf
libcaf_core/caf/scheduled_actor/flow.hpp
View file @
04e02412
...
@@ -10,6 +10,7 @@
...
@@ -10,6 +10,7 @@
#include "caf/flow/observable.hpp"
#include "caf/flow/observable.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/op/cell.hpp"
#include "caf/flow/single.hpp"
#include "caf/flow/single.hpp"
#include "caf/scheduled_actor.hpp"
#include "caf/scheduled_actor.hpp"
...
@@ -26,20 +27,18 @@ namespace caf {
...
@@ -26,20 +27,18 @@ namespace caf {
template
<
class
T
,
class
Policy
>
template
<
class
T
,
class
Policy
>
flow
::
single
<
T
>
scheduled_actor
::
single_from_response_impl
(
Policy
&
policy
)
{
flow
::
single
<
T
>
scheduled_actor
::
single_from_response_impl
(
Policy
&
policy
)
{
using
output_type
=
T
;
auto
cell
=
make_counted
<
flow
::
op
::
cell
<
T
>>
(
this
);
using
impl_type
=
typename
flow
::
single
<
output_type
>::
impl
;
auto
ptr
=
make_counted
<
impl_type
>
(
this
);
policy
.
then
(
policy
.
then
(
this
,
this
,
[
this
,
ptr
](
T
&
val
)
{
[
this
,
cell
](
T
&
val
)
{
ptr
->
set_value
(
std
::
move
(
val
));
cell
->
set_value
(
std
::
move
(
val
));
run_actions
();
run_actions
();
},
},
[
this
,
ptr
](
error
&
err
)
{
[
this
,
cell
](
error
&
err
)
{
ptr
->
set_error
(
std
::
move
(
err
));
cell
->
set_error
(
std
::
move
(
err
));
run_actions
();
run_actions
();
});
});
return
flow
::
single
<
output_type
>
{
std
::
move
(
ptr
)};
return
flow
::
single
<
T
>
{
std
::
move
(
cell
)};
}
}
}
// namespace caf
}
// namespace caf
libcaf_core/src/detail/ref_counted_base.cpp
0 → 100644
View file @
04e02412
// 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.
#include "caf/detail/ref_counted_base.hpp"
namespace
caf
::
detail
{
ref_counted_base
::~
ref_counted_base
()
{
// nop
}
ref_counted_base
::
ref_counted_base
()
:
rc_
(
1
)
{
// nop
}
ref_counted_base
::
ref_counted_base
(
const
ref_counted_base
&
)
:
rc_
(
1
)
{
// nop; intentionally don't copy the reference count
}
ref_counted_base
&
ref_counted_base
::
operator
=
(
const
ref_counted_base
&
)
{
// nop; intentionally don't copy the reference count
return
*
this
;
}
void
ref_counted_base
::
deref
()
const
noexcept
{
if
(
unique
()
||
rc_
.
fetch_sub
(
1
,
std
::
memory_order_acq_rel
)
==
1
)
delete
this
;
}
}
// namespace caf::detail
libcaf_core/src/flow/coordinated.cpp
0 → 100644
View file @
04e02412
// 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.
#include "caf/flow/coordinated.hpp"
namespace
caf
::
flow
{
coordinated
::~
coordinated
()
{
// nop
}
}
// namespace caf::flow
libcaf_core/src/flow/observable_builder.cpp
View file @
04e02412
...
@@ -4,133 +4,4 @@
...
@@ -4,133 +4,4 @@
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/observable_builder.hpp"
#include <limits>
namespace
caf
::
flow
{}
// namespace caf::flow
namespace
caf
::
flow
{
class
interval_action
:
public
ref_counted
,
public
action
::
impl
{
public:
interval_action
(
intrusive_ptr
<
interval_impl
>
impl
)
:
state_
(
action
::
state
::
scheduled
),
impl_
(
std
::
move
(
impl
))
{
// nop
}
void
dispose
()
override
{
state_
=
action
::
state
::
disposed
;
}
bool
disposed
()
const
noexcept
override
{
return
state_
.
load
()
==
action
::
state
::
disposed
;
}
action
::
state
current_state
()
const
noexcept
override
{
return
state_
.
load
();
}
void
run
()
override
{
if
(
state_
.
load
()
==
action
::
state
::
scheduled
)
impl_
->
fire
(
this
);
}
void
ref_disposable
()
const
noexcept
override
{
ref
();
}
void
deref_disposable
()
const
noexcept
override
{
deref
();
}
friend
void
intrusive_ptr_add_ref
(
const
interval_action
*
ptr
)
noexcept
{
ptr
->
ref
();
}
friend
void
intrusive_ptr_release
(
const
interval_action
*
ptr
)
noexcept
{
ptr
->
deref
();
}
private:
std
::
atomic
<
action
::
state
>
state_
;
intrusive_ptr
<
interval_impl
>
impl_
;
};
interval_impl
::
interval_impl
(
coordinator
*
ctx
,
timespan
initial_delay
,
timespan
period
)
:
interval_impl
(
ctx
,
initial_delay
,
period
,
std
::
numeric_limits
<
int64_t
>::
max
())
{
// nop
}
interval_impl
::
interval_impl
(
coordinator
*
ctx
,
timespan
initial_delay
,
timespan
period
,
int64_t
max_val
)
:
super
(
ctx
),
initial_delay_
(
initial_delay
),
period_
(
period
),
max_
(
max_val
)
{
CAF_ASSERT
(
max_val
>
0
);
}
void
interval_impl
::
dispose
()
{
if
(
obs_
)
{
obs_
.
on_complete
();
obs_
=
nullptr
;
}
if
(
pending_
)
{
pending_
.
dispose
();
pending_
=
nullptr
;
}
val_
=
max_
;
}
bool
interval_impl
::
disposed
()
const
noexcept
{
return
val_
==
max_
;
}
void
interval_impl
::
on_request
(
disposable_impl
*
ptr
,
size_t
n
)
{
if
(
obs_
.
ptr
()
==
ptr
)
{
if
(
demand_
==
0
&&
!
pending_
)
{
if
(
val_
==
0
)
last_
=
ctx_
->
steady_time
()
+
initial_delay_
;
else
last_
=
ctx_
->
steady_time
()
+
period_
;
pending_
=
ctx_
->
delay_until
(
last_
,
action
{
make_counted
<
interval_action
>
(
this
)});
}
demand_
+=
n
;
}
}
void
interval_impl
::
on_cancel
(
disposable_impl
*
ptr
)
{
if
(
obs_
.
ptr
()
==
ptr
)
{
obs_
=
nullptr
;
pending_
.
dispose
();
val_
=
max_
;
}
}
disposable
interval_impl
::
subscribe
(
observer
<
int64_t
>
sink
)
{
if
(
obs_
||
val_
==
max_
)
{
sink
.
on_error
(
make_error
(
sec
::
invalid_observable
));
return
{};
}
else
{
obs_
=
sink
;
return
super
::
do_subscribe
(
sink
.
ptr
());
}
}
void
interval_impl
::
fire
(
interval_action
*
act
)
{
if
(
obs_
)
{
--
demand_
;
obs_
.
on_next
(
val_
);
if
(
++
val_
==
max_
)
{
obs_
.
on_complete
();
obs_
=
nullptr
;
pending_
=
nullptr
;
}
else
if
(
demand_
>
0
)
{
auto
now
=
ctx_
->
steady_time
();
auto
next
=
last_
+
period_
;
while
(
next
<=
now
)
next
+=
period_
;
last_
=
next
;
pending_
=
ctx_
->
delay_until
(
next
,
action
{
act
});
}
}
}
}
// namespace caf::flow
libcaf_core/src/flow/op/interval.cpp
0 → 100644
View file @
04e02412
#include "caf/flow/op/interval.hpp"
#include "caf/flow/subscription.hpp"
#include <limits>
#include <utility>
namespace
caf
::
flow
::
op
{
class
interval_action
;
class
interval_sub_base
:
public
subscription
::
impl_base
{
public:
virtual
void
fire
(
interval_action
*
)
=
0
;
};
using
interval_sub_ptr
=
intrusive_ptr
<
interval_sub_base
>
;
class
interval_action
:
public
detail
::
ref_counted_base
,
public
action
::
impl
{
public:
interval_action
(
interval_sub_ptr
sub
)
:
state_
(
action
::
state
::
scheduled
),
sub_
(
std
::
move
(
sub
))
{
// nop
}
void
dispose
()
override
{
state_
=
action
::
state
::
disposed
;
}
bool
disposed
()
const
noexcept
override
{
return
state_
.
load
()
==
action
::
state
::
disposed
;
}
action
::
state
current_state
()
const
noexcept
override
{
return
state_
.
load
();
}
void
run
()
override
{
if
(
state_
.
load
()
==
action
::
state
::
scheduled
)
sub_
->
fire
(
this
);
}
void
ref_disposable
()
const
noexcept
override
{
ref
();
}
void
deref_disposable
()
const
noexcept
override
{
deref
();
}
private:
std
::
atomic
<
action
::
state
>
state_
;
interval_sub_ptr
sub_
;
};
class
interval_sub
:
public
interval_sub_base
{
public:
// -- constructors, destructors, and assignment operators --------------------
interval_sub
(
coordinator
*
ctx
,
timespan
initial_delay
,
timespan
period
,
int64_t
max_val
,
observer
<
int64_t
>
out
)
:
ctx_
(
ctx
),
initial_delay_
(
initial_delay
),
period_
(
period
),
max_
(
max_val
),
out_
(
std
::
move
(
out
))
{
CAF_ASSERT
(
max_val
>
0
);
}
// -- implementation of subscription_impl ------------------------------------
void
cancel
()
override
{
if
(
out_
)
{
ctx_
->
delay_fn
([
ptr
=
strong_this
()]
{
ptr
->
do_cancel
();
});
}
}
bool
disposed
()
const
noexcept
override
{
return
!
out_
;
}
void
request
(
size_t
n
)
override
{
demand_
+=
n
;
if
(
!
pending_
)
{
if
(
val_
==
0
)
last_
=
ctx_
->
steady_time
()
+
initial_delay_
;
else
last_
=
ctx_
->
steady_time
()
+
period_
;
pending_
=
ctx_
->
delay_until
(
last_
,
action
{
make_counted
<
interval_action
>
(
this
)});
}
}
void
fire
(
interval_action
*
act
)
override
{
if
(
out_
)
{
--
demand_
;
out_
.
on_next
(
val_
);
if
(
++
val_
==
max_
)
{
out_
.
on_complete
();
out_
=
nullptr
;
pending_
=
nullptr
;
}
else
if
(
demand_
>
0
)
{
auto
now
=
ctx_
->
steady_time
();
auto
next
=
last_
+
period_
;
while
(
next
<=
now
)
next
+=
period_
;
last_
=
next
;
pending_
=
ctx_
->
delay_until
(
next
,
action
{
act
});
}
else
{
pending_
=
nullptr
;
}
}
}
private:
void
do_cancel
()
{
if
(
out_
)
{
out_
.
on_complete
();
out_
=
nullptr
;
}
if
(
pending_
)
{
pending_
.
dispose
();
pending_
=
nullptr
;
}
}
intrusive_ptr
<
interval_sub
>
strong_this
()
{
return
intrusive_ptr
<
interval_sub
>
{
this
};
}
coordinator
*
ctx_
;
disposable
pending_
;
timespan
initial_delay_
;
timespan
period_
;
coordinator
::
steady_time_point
last_
;
int64_t
val_
=
0
;
int64_t
max_
;
size_t
demand_
=
0
;
observer
<
int64_t
>
out_
;
};
interval
::
interval
(
coordinator
*
ctx
,
timespan
initial_delay
,
timespan
period
)
:
interval
(
ctx
,
initial_delay
,
period
,
std
::
numeric_limits
<
int64_t
>::
max
())
{
// nop
}
interval
::
interval
(
coordinator
*
ctx
,
timespan
initial_delay
,
timespan
period
,
int64_t
max_val
)
:
super
(
ctx
),
initial_delay_
(
initial_delay
),
period_
(
period
),
max_
(
max_val
)
{
// nop
}
disposable
interval
::
subscribe
(
observer
<
int64_t
>
out
)
{
// Intervals introduce a time dependency, so we need to watch them in order
// to prevent actors from shutting down while timeouts are still pending.
auto
ptr
=
make_counted
<
interval_sub
>
(
ctx_
,
initial_delay_
,
period_
,
max_
,
out
);
ctx_
->
watch
(
ptr
->
as_disposable
());
out
.
on_subscribe
(
subscription
{
ptr
});
return
ptr
->
as_disposable
();
}
}
// namespace caf::flow::op
libcaf_core/src/flow/subscription.cpp
View file @
04e02412
...
@@ -16,50 +16,30 @@ void subscription::impl::dispose() {
...
@@ -16,50 +16,30 @@ void subscription::impl::dispose() {
cancel
();
cancel
();
}
}
bool
subscription
::
nop_impl
::
disposed
()
const
noexcept
{
void
subscription
::
impl_base
::
ref_disposable
()
const
noexcept
{
return
disposed_
;
this
->
ref
();
}
void
subscription
::
nop_impl
::
ref_disposable
()
const
noexcept
{
ref
();
}
void
subscription
::
nop_impl
::
deref_disposable
()
const
noexcept
{
deref
();
}
void
subscription
::
nop_impl
::
cancel
()
{
disposed_
=
true
;
}
}
void
subscription
::
nop_impl
::
request
(
size_t
)
{
void
subscription
::
impl_base
::
deref_disposable
()
const
noexcept
{
// nop
this
->
deref
();
}
}
subscription
::
listener
::~
listener
()
{
subscription
::
listener
::~
listener
()
{
// nop
// nop
}
}
bool
subscription
::
default
_impl
::
disposed
()
const
noexcept
{
bool
subscription
::
fwd
_impl
::
disposed
()
const
noexcept
{
return
src_
==
nullptr
;
return
src_
==
nullptr
;
}
}
void
subscription
::
default_impl
::
ref_disposable
()
const
noexcept
{
void
subscription
::
fwd_impl
::
request
(
size_t
n
)
{
this
->
ref
();
}
void
subscription
::
default_impl
::
deref_disposable
()
const
noexcept
{
this
->
deref
();
}
void
subscription
::
default_impl
::
request
(
size_t
n
)
{
if
(
src_
)
if
(
src_
)
ctx
()
->
delay_fn
([
src
=
src_
,
snk
=
snk_
,
n
]
{
//
ctx
()
->
delay_fn
([
src
=
src_
,
snk
=
snk_
,
n
]
{
//
src
->
on_request
(
snk
.
get
(),
n
);
src
->
on_request
(
snk
.
get
(),
n
);
});
});
}
}
void
subscription
::
default
_impl
::
cancel
()
{
void
subscription
::
fwd
_impl
::
cancel
()
{
if
(
src_
)
{
if
(
src_
)
{
ctx
()
->
delay_fn
([
src
=
src_
,
snk
=
snk_
]
{
//
ctx
()
->
delay_fn
([
src
=
src_
,
snk
=
snk_
]
{
//
src
->
on_cancel
(
snk
.
get
());
src
->
on_cancel
(
snk
.
get
());
...
...
libcaf_core/src/ref_counted.cpp
View file @
04e02412
...
@@ -10,22 +10,4 @@ ref_counted::~ref_counted() {
...
@@ -10,22 +10,4 @@ ref_counted::~ref_counted() {
// nop
// nop
}
}
ref_counted
::
ref_counted
()
:
rc_
(
1
)
{
// nop
}
ref_counted
::
ref_counted
(
const
ref_counted
&
)
:
rc_
(
1
)
{
// nop; don't copy reference count
}
ref_counted
&
ref_counted
::
operator
=
(
const
ref_counted
&
)
{
// nop; intentionally don't copy reference count
return
*
this
;
}
void
ref_counted
::
deref
()
const
noexcept
{
if
(
unique
()
||
rc_
.
fetch_sub
(
1
,
std
::
memory_order_acq_rel
)
==
1
)
delete
this
;
}
}
// namespace caf
}
// namespace caf
libcaf_core/test/flow/concat.cpp
View file @
04e02412
...
@@ -28,99 +28,6 @@ struct fixture : test_coordinator_fixture<> {
...
@@ -28,99 +28,6 @@ struct fixture : test_coordinator_fixture<> {
BEGIN_FIXTURE_SCOPE
(
fixture
)
BEGIN_FIXTURE_SCOPE
(
fixture
)
SCENARIO
(
"concatenate processes inputs sequentially"
)
{
GIVEN
(
"a concatenation with no inputs and shutdown-on-last-complete ON"
)
{
auto
uut
=
make_counted
<
flow
::
concat_impl
<
int
>>
(
ctx
.
get
());
WHEN
(
"subscribing to the concatenation"
)
{
THEN
(
"the concatenation immediately closes"
)
{
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
uut
->
subscribe
(
snk
->
as_observer
());
ctx
->
run
();
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
aborted
);
CHECK_EQ
(
snk
->
err
,
sec
::
disposed
);
CHECK
(
snk
->
buf
.
empty
());
}
}
}
GIVEN
(
"a concatenation with no inputs and shutdown-on-last-complete OFF"
)
{
auto
uut
=
make_counted
<
flow
::
concat_impl
<
int
>>
(
ctx
.
get
());
uut
->
shutdown_on_last_complete
(
false
);
WHEN
(
"subscribing to the concatenation"
)
{
THEN
(
"the concatenation accepts the subscription and does nothing else"
)
{
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
uut
->
subscribe
(
snk
->
as_observer
());
ctx
->
run
();
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK
(
snk
->
buf
.
empty
());
uut
->
dispose
();
ctx
->
run
();
}
}
}
GIVEN
(
"a concatenation with one input that completes"
)
{
WHEN
(
"subscribing and requesting before the first push"
)
{
auto
uut
=
make_counted
<
flow
::
concat_impl
<
int
>>
(
ctx
.
get
());
auto
src
=
flow
::
make_passive_observable
<
int
>
(
ctx
.
get
());
uut
->
add
(
src
->
as_observable
());
ctx
->
run
();
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
uut
->
subscribe
(
snk
->
as_observer
());
ctx
->
run
();
THEN
(
"the concatenation forwards all items from the source"
)
{
MESSAGE
(
"the observer enters the state subscribed"
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
buf
,
ls
());
MESSAGE
(
"when requesting data, no data is received yet"
);
snk
->
sub
.
request
(
2
);
ctx
->
run
();
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
buf
,
ls
());
MESSAGE
(
"after pushing, the observer immediately receives them"
);
src
->
push
(
1
,
2
);
ctx
->
run
();
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
buf
,
ls
(
1
,
2
));
MESSAGE
(
"when requesting more data, the observer gets the remainder"
);
snk
->
sub
.
request
(
20
);
ctx
->
run
();
src
->
push
(
3
,
4
,
5
);
ctx
->
run
();
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
buf
,
ls
(
1
,
2
,
3
,
4
,
5
));
MESSAGE
(
"the concatenation closes if the source closes"
);
src
->
complete
();
ctx
->
run
();
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
completed
);
CHECK_EQ
(
snk
->
buf
,
ls
(
1
,
2
,
3
,
4
,
5
));
}
}
}
GIVEN
(
"a concatenation with one input that aborts after some items"
)
{
WHEN
(
"subscribing to the concatenation"
)
{
auto
uut
=
make_counted
<
flow
::
concat_impl
<
int
>>
(
ctx
.
get
());
auto
src
=
flow
::
make_passive_observable
<
int
>
(
ctx
.
get
());
uut
->
add
(
src
->
as_observable
());
ctx
->
run
();
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
uut
->
subscribe
(
snk
->
as_observer
());
ctx
->
run
();
THEN
(
"the concatenation forwards all items until the error"
)
{
MESSAGE
(
"after the source pushed five items, it emits an error"
);
snk
->
sub
.
request
(
20
);
ctx
->
run
();
src
->
push
(
1
,
2
,
3
,
4
,
5
);
ctx
->
run
();
src
->
abort
(
make_error
(
sec
::
runtime_error
));
ctx
->
run
();
MESSAGE
(
"the observer obtains the and then the error"
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
aborted
);
CHECK_EQ
(
snk
->
buf
,
ls
(
1
,
2
,
3
,
4
,
5
));
CHECK_EQ
(
snk
->
err
,
make_error
(
sec
::
runtime_error
));
}
}
}
}
SCENARIO
(
"concat operators combine inputs"
)
{
SCENARIO
(
"concat operators combine inputs"
)
{
GIVEN
(
"two observables"
)
{
GIVEN
(
"two observables"
)
{
WHEN
(
"merging them to a single publisher via concat"
)
{
WHEN
(
"merging them to a single publisher via concat"
)
{
...
@@ -128,9 +35,9 @@ SCENARIO("concat operators combine inputs") {
...
@@ -128,9 +35,9 @@ SCENARIO("concat operators combine inputs") {
auto
outputs
=
std
::
vector
<
int
>
{};
auto
outputs
=
std
::
vector
<
int
>
{};
auto
r1
=
ctx
->
make_observable
().
repeat
(
11
).
take
(
113
);
auto
r1
=
ctx
->
make_observable
().
repeat
(
11
).
take
(
113
);
auto
r2
=
ctx
->
make_observable
().
repeat
(
22
).
take
(
223
);
auto
r2
=
ctx
->
make_observable
().
repeat
(
22
).
take
(
223
);
flow
::
concat
(
std
::
move
(
r1
),
std
::
move
(
r2
)).
for_each
([
&
outputs
](
int
x
)
{
ctx
->
make_observable
()
outputs
.
emplace_back
(
x
);
.
concat
(
std
::
move
(
r1
),
std
::
move
(
r2
))
});
.
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
});
ctx
->
run
();
ctx
->
run
();
if
(
CHECK_EQ
(
outputs
.
size
(),
336u
))
{
if
(
CHECK_EQ
(
outputs
.
size
(),
336u
))
{
CHECK
(
std
::
all_of
(
outputs
.
begin
(),
outputs
.
begin
()
+
113
,
CHECK
(
std
::
all_of
(
outputs
.
begin
(),
outputs
.
begin
()
+
113
,
...
...
libcaf_core/test/flow/concat_map.cpp
View file @
04e02412
...
@@ -78,8 +78,8 @@ SCENARIO("concat_map merges multiple observables") {
...
@@ -78,8 +78,8 @@ SCENARIO("concat_map merges multiple observables") {
std
::
iota
(
inputs
.
begin
(),
inputs
.
end
(),
0
);
std
::
iota
(
inputs
.
begin
(),
inputs
.
end
(),
0
);
self
->
make_observable
()
self
->
make_observable
()
.
from_container
(
inputs
)
.
from_container
(
inputs
)
.
concat_map
([
self
{
self
},
add1
{
adder
}
](
int32_t
x
)
{
.
concat_map
([
self
=
self
,
adder
](
int32_t
x
)
{
return
self
->
request
(
add
1
,
infinite
,
x
).
as_observable
<
int32_t
>
();
return
self
->
request
(
add
er
,
infinite
,
x
).
as_observable
<
int32_t
>
();
})
})
.
for_each
([
&
outputs
](
int32_t
x
)
{
outputs
.
emplace_back
(
x
);
});
.
for_each
([
&
outputs
](
int32_t
x
)
{
outputs
.
emplace_back
(
x
);
});
launch
();
launch
();
...
...
libcaf_core/test/flow/
error
.cpp
→
libcaf_core/test/flow/
fail
.cpp
View file @
04e02412
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
// the main distribution directory for license terms and copyright or visit
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE flow.
error
#define CAF_SUITE flow.
fail
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/observable_builder.hpp"
...
@@ -22,12 +22,12 @@ struct fixture : test_coordinator_fixture<> {
...
@@ -22,12 +22,12 @@ struct fixture : test_coordinator_fixture<> {
BEGIN_FIXTURE_SCOPE
(
fixture
)
BEGIN_FIXTURE_SCOPE
(
fixture
)
SCENARIO
(
"
an error observable
immediately calls on_error on any subscriber"
)
{
SCENARIO
(
"
the fail operator
immediately calls on_error on any subscriber"
)
{
GIVEN
(
"a
n error<int32>
"
)
{
GIVEN
(
"a
fail<int32> operator
"
)
{
WHEN
(
"an observer subscribes"
)
{
WHEN
(
"an observer subscribes"
)
{
THEN
(
"the observer receives on_error"
)
{
THEN
(
"the observer receives on_error"
)
{
auto
uut
=
ctx
->
make_observable
().
error
<
int32_t
>
(
sec
::
runtime_error
);
auto
uut
=
ctx
->
make_observable
().
fail
<
int32_t
>
(
sec
::
runtime_error
);
auto
snk
=
flow
::
make_
passive
_observer
<
int32_t
>
();
auto
snk
=
flow
::
make_
auto
_observer
<
int32_t
>
();
uut
.
subscribe
(
snk
->
as_observer
());
uut
.
subscribe
(
snk
->
as_observer
());
ctx
->
run
();
ctx
->
run
();
CHECK
(
!
snk
->
sub
);
CHECK
(
!
snk
->
sub
);
...
...
libcaf_core/test/flow/item_publisher.cpp
0 → 100644
View file @
04e02412
// 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.
#define CAF_SUITE flow.item_publisher
#include "caf/flow/item_publisher.hpp"
#include "core-test.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/op/merge.hpp"
#include "caf/flow/scoped_coordinator.hpp"
using
namespace
caf
;
namespace
{
struct
fixture
:
test_coordinator_fixture
<>
{
flow
::
scoped_coordinator_ptr
ctx
=
flow
::
make_scoped_coordinator
();
template
<
class
...
Ts
>
std
::
vector
<
int
>
ls
(
Ts
...
xs
)
{
return
std
::
vector
<
int
>
{
xs
...};
}
};
}
// namespace
BEGIN_FIXTURE_SCOPE
(
fixture
)
SCENARIO
(
"item publishers discard items that arrive before a subscriber"
)
{
GIVEN
(
"an item publisher"
)
{
WHEN
(
"publishing items"
)
{
THEN
(
"observers see only items that were published after subscribing"
)
{
auto
uut
=
flow
::
item_publisher
<
int
>
{
ctx
.
get
()};
uut
.
push
({
1
,
2
,
3
});
auto
snk
=
flow
::
make_auto_observer
<
int
>
();
uut
.
subscribe
(
snk
->
as_observer
());
ctx
->
run
();
uut
.
push
({
4
,
5
,
6
});
ctx
->
run
();
uut
.
close
();
CHECK_EQ
(
snk
->
buf
,
ls
(
4
,
5
,
6
));
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
completed
);
}
}
}
}
END_FIXTURE_SCOPE
()
libcaf_core/test/flow/merge.cpp
View file @
04e02412
...
@@ -8,7 +8,9 @@
...
@@ -8,7 +8,9 @@
#include "core-test.hpp"
#include "core-test.hpp"
#include "caf/flow/item_publisher.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/op/merge.hpp"
#include "caf/flow/scoped_coordinator.hpp"
#include "caf/flow/scoped_coordinator.hpp"
using
namespace
caf
;
using
namespace
caf
;
...
@@ -22,6 +24,13 @@ struct fixture : test_coordinator_fixture<> {
...
@@ -22,6 +24,13 @@ struct fixture : test_coordinator_fixture<> {
std
::
vector
<
int
>
ls
(
Ts
...
xs
)
{
std
::
vector
<
int
>
ls
(
Ts
...
xs
)
{
return
std
::
vector
<
int
>
{
xs
...};
return
std
::
vector
<
int
>
{
xs
...};
}
}
template
<
class
T
>
std
::
vector
<
T
>
concat
(
std
::
vector
<
T
>
xs
,
std
::
vector
<
T
>
ys
)
{
for
(
auto
&
y
:
ys
)
xs
.
push_back
(
y
);
return
xs
;
}
};
};
}
// namespace
}
// namespace
...
@@ -29,40 +38,23 @@ struct fixture : test_coordinator_fixture<> {
...
@@ -29,40 +38,23 @@ struct fixture : test_coordinator_fixture<> {
BEGIN_FIXTURE_SCOPE
(
fixture
)
BEGIN_FIXTURE_SCOPE
(
fixture
)
SCENARIO
(
"mergers round-robin over their inputs"
)
{
SCENARIO
(
"mergers round-robin over their inputs"
)
{
GIVEN
(
"a merger with no inputs
and shutdown-on-last-complete ON
"
)
{
GIVEN
(
"a merger with no inputs"
)
{
auto
uut
=
make_counted
<
flow
::
merger_impl
<
int
>>
(
ctx
.
get
());
auto
uut
=
flow
::
make_observable
<
flow
::
op
::
merge
<
int
>>
(
ctx
.
get
());
WHEN
(
"subscribing to the merger"
)
{
WHEN
(
"subscribing to the merger"
)
{
THEN
(
"the merger immediately closes"
)
{
THEN
(
"the merger immediately closes"
)
{
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
auto
snk
=
flow
::
make_auto_observer
<
int
>
();
uut
->
subscribe
(
snk
->
as_observer
());
uut
.
subscribe
(
snk
->
as_observer
());
ctx
->
run
();
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
aborted
);
CHECK_EQ
(
snk
->
err
,
sec
::
disposed
);
CHECK
(
snk
->
buf
.
empty
());
}
}
}
GIVEN
(
"a merger with no inputs and shutdown-on-last-complete OFF"
)
{
auto
uut
=
make_counted
<
flow
::
merger_impl
<
int
>>
(
ctx
.
get
());
uut
->
shutdown_on_last_complete
(
false
);
WHEN
(
"subscribing to the merger"
)
{
THEN
(
"the merger accepts the subscription and does nothing else"
)
{
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
uut
->
subscribe
(
snk
->
as_observer
());
ctx
->
run
();
ctx
->
run
();
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscrib
ed
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
complet
ed
);
CHECK
(
snk
->
buf
.
empty
());
CHECK
(
snk
->
buf
.
empty
());
uut
->
dispose
();
ctx
->
run
();
}
}
}
}
}
}
GIVEN
(
"a round-robin merger with one input that completes"
)
{
GIVEN
(
"a round-robin merger with one input that completes"
)
{
WHEN
(
"subscribing to the merger and requesting before the first push"
)
{
WHEN
(
"subscribing to the merger and requesting before the first push"
)
{
auto
uut
=
make_counted
<
flow
::
merger_impl
<
int
>>
(
ctx
.
get
());
auto
src
=
flow
::
item_publisher
<
int
>
{
ctx
.
get
()};
auto
src
=
flow
::
make_passive_observable
<
int
>
(
ctx
.
get
());
auto
uut
=
make_counted
<
flow
::
op
::
merge
<
int
>>
(
ctx
.
get
(),
uut
->
add
(
src
->
as_observable
());
src
.
as_observable
());
ctx
->
run
();
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
uut
->
subscribe
(
snk
->
as_observer
());
uut
->
subscribe
(
snk
->
as_observer
());
ctx
->
run
();
ctx
->
run
();
...
@@ -76,7 +68,7 @@ SCENARIO("mergers round-robin over their inputs") {
...
@@ -76,7 +68,7 @@ SCENARIO("mergers round-robin over their inputs") {
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
buf
,
ls
());
CHECK_EQ
(
snk
->
buf
,
ls
());
MESSAGE
(
"after pushing, the observer immediately receives them"
);
MESSAGE
(
"after pushing, the observer immediately receives them"
);
src
->
push
(
1
,
2
,
3
,
4
,
5
);
src
.
push
({
1
,
2
,
3
,
4
,
5
}
);
ctx
->
run
();
ctx
->
run
();
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
buf
,
ls
(
1
,
2
));
CHECK_EQ
(
snk
->
buf
,
ls
(
1
,
2
));
...
@@ -86,16 +78,16 @@ SCENARIO("mergers round-robin over their inputs") {
...
@@ -86,16 +78,16 @@ SCENARIO("mergers round-robin over their inputs") {
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
buf
,
ls
(
1
,
2
,
3
,
4
,
5
));
CHECK_EQ
(
snk
->
buf
,
ls
(
1
,
2
,
3
,
4
,
5
));
MESSAGE
(
"the merger closes if the source closes"
);
MESSAGE
(
"the merger closes if the source closes"
);
src
->
complet
e
();
src
.
clos
e
();
ctx
->
run
();
ctx
->
run
();
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
completed
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
completed
);
CHECK_EQ
(
snk
->
buf
,
ls
(
1
,
2
,
3
,
4
,
5
));
CHECK_EQ
(
snk
->
buf
,
ls
(
1
,
2
,
3
,
4
,
5
));
}
}
}
}
AND_WHEN
(
"subscribing to the merger pushing before the first request"
)
{
AND_WHEN
(
"subscribing to the merger pushing before the first request"
)
{
auto
uut
=
make_counted
<
flow
::
merger_impl
<
int
>>
(
ctx
.
get
())
;
auto
src
=
flow
::
item_publisher
<
int
>
{
ctx
.
get
()}
;
auto
src
=
flow
::
make_passive_observable
<
int
>
(
ctx
.
get
());
auto
uut
=
make_counted
<
flow
::
op
::
merge
<
int
>>
(
ctx
.
get
(),
uut
->
add
(
src
->
as_observable
());
src
.
as_observable
());
ctx
->
run
();
ctx
->
run
();
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
uut
->
subscribe
(
snk
->
as_observer
());
uut
->
subscribe
(
snk
->
as_observer
());
...
@@ -105,7 +97,7 @@ SCENARIO("mergers round-robin over their inputs") {
...
@@ -105,7 +97,7 @@ SCENARIO("mergers round-robin over their inputs") {
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
buf
,
ls
());
CHECK_EQ
(
snk
->
buf
,
ls
());
MESSAGE
(
"after pushing, the observer receives nothing yet"
);
MESSAGE
(
"after pushing, the observer receives nothing yet"
);
src
->
push
(
1
,
2
,
3
,
4
,
5
);
src
.
push
({
1
,
2
,
3
,
4
,
5
}
);
ctx
->
run
();
ctx
->
run
();
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
buf
,
ls
());
CHECK_EQ
(
snk
->
buf
,
ls
());
...
@@ -120,7 +112,7 @@ SCENARIO("mergers round-robin over their inputs") {
...
@@ -120,7 +112,7 @@ SCENARIO("mergers round-robin over their inputs") {
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
buf
,
ls
(
1
,
2
,
3
,
4
,
5
));
CHECK_EQ
(
snk
->
buf
,
ls
(
1
,
2
,
3
,
4
,
5
));
MESSAGE
(
"the merger closes if the source closes"
);
MESSAGE
(
"the merger closes if the source closes"
);
src
->
complet
e
();
src
.
clos
e
();
ctx
->
run
();
ctx
->
run
();
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
completed
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
completed
);
CHECK_EQ
(
snk
->
buf
,
ls
(
1
,
2
,
3
,
4
,
5
));
CHECK_EQ
(
snk
->
buf
,
ls
(
1
,
2
,
3
,
4
,
5
));
...
@@ -129,18 +121,17 @@ SCENARIO("mergers round-robin over their inputs") {
...
@@ -129,18 +121,17 @@ SCENARIO("mergers round-robin over their inputs") {
}
}
GIVEN
(
"a round-robin merger with one input that aborts after some items"
)
{
GIVEN
(
"a round-robin merger with one input that aborts after some items"
)
{
WHEN
(
"subscribing to the merger"
)
{
WHEN
(
"subscribing to the merger"
)
{
auto
uut
=
make_counted
<
flow
::
merger_impl
<
int
>>
(
ctx
.
get
());
auto
src
=
flow
::
item_publisher
<
int
>
{
ctx
.
get
()};
auto
src
=
flow
::
make_passive_observable
<
int
>
(
ctx
.
get
());
auto
uut
=
make_counted
<
flow
::
op
::
merge
<
int
>>
(
ctx
.
get
(),
uut
->
add
(
src
->
as_observable
());
src
.
as_observable
());
ctx
->
run
();
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
uut
->
subscribe
(
snk
->
as_observer
());
uut
->
subscribe
(
snk
->
as_observer
());
ctx
->
run
();
ctx
->
run
();
THEN
(
"the merger forwards all items from the source until the error"
)
{
THEN
(
"the merger forwards all items from the source until the error"
)
{
MESSAGE
(
"after the source pushed five items, it emits an error"
);
MESSAGE
(
"after the source pushed five items, it emits an error"
);
src
->
push
(
1
,
2
,
3
,
4
,
5
);
src
.
push
({
1
,
2
,
3
,
4
,
5
}
);
ctx
->
run
();
ctx
->
run
();
src
->
abort
(
make_error
(
sec
::
runtime_error
));
src
.
abort
(
make_error
(
sec
::
runtime_error
));
ctx
->
run
();
ctx
->
run
();
MESSAGE
(
"when requesting, the observer still obtains the items first"
);
MESSAGE
(
"when requesting, the observer still obtains the items first"
);
snk
->
sub
.
request
(
2
);
snk
->
sub
.
request
(
2
);
...
@@ -155,61 +146,41 @@ SCENARIO("mergers round-robin over their inputs") {
...
@@ -155,61 +146,41 @@ SCENARIO("mergers round-robin over their inputs") {
}
}
}
}
}
}
}
GIVEN
(
"a merger that operates on an observable of observables"
)
{
WHEN
(
"subscribing to the merger"
)
{
SCENARIO
(
"merge operators combine inputs"
)
{
THEN
(
"the subscribers receives all values from all observables"
)
{
GIVEN
(
"two observables"
)
{
auto
inputs
=
std
::
vector
<
flow
::
observable
<
int
>>
{
WHEN
(
"merging them to a single publisher"
)
{
ctx
->
make_observable
().
iota
(
1
).
take
(
3
).
as_observable
(),
THEN
(
"the observer receives the output of both sources"
)
{
ctx
->
make_observable
().
iota
(
4
).
take
(
3
).
as_observable
(),
auto
on_complete_called
=
false
;
ctx
->
make_observable
().
iota
(
7
).
take
(
3
).
as_observable
(),
auto
outputs
=
std
::
vector
<
int
>
{};
};
auto
r1
=
ctx
->
make_observable
().
repeat
(
11
).
take
(
113
);
auto
snk
=
flow
::
make_auto_observer
<
int
>
();
auto
r2
=
ctx
->
make_observable
().
repeat
(
22
).
take
(
223
);
ctx
->
make_observable
()
flow
::
merge
(
std
::
move
(
r1
),
std
::
move
(
r2
))
.
from_container
(
std
::
move
(
inputs
))
.
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
},
.
merge
()
[](
const
error
&
err
)
{
FAIL
(
"unexpected error:"
<<
err
);
},
.
subscribe
(
snk
->
as_observer
());
[
&
on_complete_called
]
{
on_complete_called
=
true
;
});
ctx
->
run
();
ctx
->
run
();
std
::
sort
(
snk
->
buf
.
begin
(),
snk
->
buf
.
end
());
CHECK
(
on_complete_called
);
CHECK_EQ
(
snk
->
buf
,
ls
(
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
));
if
(
CHECK_EQ
(
outputs
.
size
(),
336u
))
{
std
::
sort
(
outputs
.
begin
(),
outputs
.
end
());
CHECK
(
std
::
all_of
(
outputs
.
begin
(),
outputs
.
begin
()
+
113
,
[](
int
x
)
{
return
x
==
11
;
}));
CHECK
(
std
::
all_of
(
outputs
.
begin
()
+
113
,
outputs
.
end
(),
[](
int
x
)
{
return
x
==
22
;
}));
}
}
}
}
}
}
}
}
}
SCENARIO
(
"mergers can delay shutdown"
)
{
SCENARIO
(
"the merge operator combine inputs"
)
{
GIVEN
(
"a merger with two inputs and shutdown_on_last_complete set to false"
)
{
GIVEN
(
"two observables"
)
{
WHEN
(
"both inputs completed"
)
{
WHEN
(
"merging them to a single observable"
)
{
THEN
(
"the merger only closes after enabling shutdown_on_last_complete"
)
{
THEN
(
"the observer receives the output of both sources"
)
{
auto
on_complete_called
=
false
;
using
ivec
=
std
::
vector
<
int
>
;
auto
outputs
=
std
::
vector
<
int
>
{};
auto
snk
=
flow
::
make_auto_observer
<
int
>
();
auto
merger
=
make_counted
<
flow
::
merger_impl
<
int
>>
(
ctx
.
get
());
ctx
->
make_observable
()
merger
->
shutdown_on_last_complete
(
false
);
.
repeat
(
11
)
merger
->
add
(
ctx
->
make_observable
().
repeat
(
11
).
take
(
113
));
.
take
(
113
)
merger
->
add
(
ctx
->
make_observable
().
repeat
(
22
).
take
(
223
));
.
merge
(
ctx
->
make_observable
().
repeat
(
22
).
take
(
223
))
merger
//
.
subscribe
(
snk
->
as_observer
());
->
as_observable
()
.
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
},
[](
const
error
&
err
)
{
FAIL
(
"unexpected error:"
<<
err
);
},
[
&
on_complete_called
]
{
on_complete_called
=
true
;
});
ctx
->
run
();
CHECK
(
!
on_complete_called
);
if
(
CHECK_EQ
(
outputs
.
size
(),
336u
))
{
std
::
sort
(
outputs
.
begin
(),
outputs
.
end
());
CHECK
(
std
::
all_of
(
outputs
.
begin
(),
outputs
.
begin
()
+
113
,
[](
int
x
)
{
return
x
==
11
;
}));
CHECK
(
std
::
all_of
(
outputs
.
begin
()
+
113
,
outputs
.
end
(),
[](
int
x
)
{
return
x
==
22
;
}));
}
merger
->
shutdown_on_last_complete
(
true
);
ctx
->
run
();
ctx
->
run
();
CHECK
(
on_complete_called
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
completed
);
CHECK_EQ
(
snk
->
sorted_buf
(),
concat
(
ivec
(
113
,
11
),
ivec
(
223
,
22
)));
}
}
}
}
}
}
...
...
libcaf_core/test/flow/mixed.cpp
0 → 100644
View file @
04e02412
// 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.
// Unlike the other test suites, this one does not focus on a single operator.
// Instead, this test suite uses the API to solve some higher level problems to
// exercise a larger chunk of the API all at once.
#define CAF_SUITE flow.mixed
#include "core-test.hpp"
#include "caf/flow/merge.hpp"
#include "caf/flow/observable.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/scoped_coordinator.hpp"
using
namespace
caf
;
using
caf
::
flow
::
make_observer
;
namespace
{
struct
fixture
:
test_coordinator_fixture
<>
{
flow
::
scoped_coordinator_ptr
ctx
=
flow
::
make_scoped_coordinator
();
template
<
class
...
Ts
>
std
::
vector
<
int
>
ls
(
Ts
...
xs
)
{
return
std
::
vector
<
int
>
{
xs
...};
}
};
}
// namespace
#define SUB_CASE(text) \
MESSAGE(text); \
if (true)
BEGIN_FIXTURE_SCOPE
(
fixture
)
SCENARIO
(
"sum up all the multiples of 3 or 5 below 1000"
)
{
SUB_CASE
(
"solution 1"
)
{
auto
snk
=
flow
::
make_auto_observer
<
int
>
();
ctx
->
make_observable
()
.
range
(
1
,
999
)
.
filter
([](
int
x
)
{
return
x
%
3
==
0
||
x
%
5
==
0
;
})
.
sum
()
.
subscribe
(
snk
->
as_observer
());
ctx
->
run
();
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
completed
);
CHECK_EQ
(
snk
->
buf
,
ls
(
233'168
));
}
SUB_CASE
(
"solution 2"
)
{
auto
snk
=
flow
::
make_auto_observer
<
int
>
();
ctx
->
make_observable
()
.
merge
(
ctx
->
make_observable
()
.
iota
(
1
)
.
map
([](
int
x
)
{
return
x
*
3
;
})
.
take_while
([](
int
x
)
{
return
x
<
1'000
;
}),
ctx
->
make_observable
()
.
iota
(
1
)
.
map
([](
int
x
)
{
return
x
*
5
;
})
.
take_while
([](
int
x
)
{
return
x
<
1'000
;
}))
.
distinct
()
.
sum
()
.
subscribe
(
snk
->
as_observer
());
ctx
->
run
();
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
completed
);
CHECK_EQ
(
snk
->
buf
,
ls
(
233'168
));
}
}
END_FIXTURE_SCOPE
()
libcaf_core/test/flow/never.cpp
View file @
04e02412
...
@@ -22,20 +22,16 @@ struct fixture : test_coordinator_fixture<> {
...
@@ -22,20 +22,16 @@ struct fixture : test_coordinator_fixture<> {
BEGIN_FIXTURE_SCOPE
(
fixture
)
BEGIN_FIXTURE_SCOPE
(
fixture
)
SCENARIO
(
"
a mute observable never invokes any
callbacks except when disposed"
)
{
SCENARIO
(
"
the never operator never invokes
callbacks except when disposed"
)
{
GIVEN
(
"a never<int32>"
)
{
GIVEN
(
"a never<int32>"
)
{
WHEN
(
"an observer subscribes"
)
{
WHEN
(
"an observer subscribes"
)
{
THEN
(
"the observer never receives any events"
)
{
THEN
(
"the observer never receives any events"
)
{
auto
uut
=
ctx
->
make_observable
().
never
<
int32_t
>
();
auto
uut
=
ctx
->
make_observable
().
never
<
int32_t
>
();
auto
snk
=
flow
::
make_
passive
_observer
<
int32_t
>
();
auto
snk
=
flow
::
make_
auto
_observer
<
int32_t
>
();
uut
.
subscribe
(
snk
->
as_observer
());
uut
.
subscribe
(
snk
->
as_observer
());
ctx
->
run
();
ctx
->
run
();
if
(
CHECK
(
snk
->
sub
))
{
CHECK
(
snk
->
buf
.
empty
());
snk
->
sub
.
request
(
42
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
ctx
->
run
();
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK
(
snk
->
buf
.
empty
());
}
}
}
}
}
}
}
...
@@ -43,21 +39,19 @@ SCENARIO("a mute observable never invokes any callbacks except when disposed") {
...
@@ -43,21 +39,19 @@ SCENARIO("a mute observable never invokes any callbacks except when disposed") {
WHEN
(
"an observer subscribes"
)
{
WHEN
(
"an observer subscribes"
)
{
THEN
(
"the observer receives on_complete"
)
{
THEN
(
"the observer receives on_complete"
)
{
auto
uut
=
ctx
->
make_observable
().
never
<
int32_t
>
();
auto
uut
=
ctx
->
make_observable
().
never
<
int32_t
>
();
auto
snk1
=
flow
::
make_
passive
_observer
<
int32_t
>
();
auto
snk1
=
flow
::
make_
auto
_observer
<
int32_t
>
();
auto
snk2
=
flow
::
make_
passive
_observer
<
int32_t
>
();
auto
snk2
=
flow
::
make_
auto
_observer
<
int32_t
>
();
uut
.
subscribe
(
snk1
->
as_observer
());
auto
sub
=
uut
.
subscribe
(
snk1
->
as_observer
());
ctx
->
run
();
ctx
->
run
();
if
(
CHECK
(
snk1
->
sub
))
{
CHECK
(
snk1
->
buf
.
empty
());
snk1
->
sub
.
request
(
42
);
CHECK_EQ
(
snk1
->
state
,
flow
::
observer_state
::
subscribed
);
ctx
->
run
();
sub
.
dispose
();
CHECK_EQ
(
snk1
->
state
,
flow
::
observer_state
::
subscribed
);
ctx
->
run
();
CHECK
(
snk1
->
buf
.
empty
());
CHECK_EQ
(
snk1
->
state
,
flow
::
observer_state
::
completed
);
uut
.
dispose
();
MESSAGE
(
"dispose only affects the subscription, "
ctx
->
run
();
"the never operator remains unchanged"
);
CHECK_EQ
(
snk1
->
state
,
flow
::
observer_state
::
completed
);
uut
.
subscribe
(
snk2
->
as_observer
());
uut
.
subscribe
(
snk2
->
as_observer
());
CHECK_EQ
(
snk2
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk2
->
state
,
flow
::
observer_state
::
aborted
);
}
}
}
}
}
}
}
...
...
libcaf_core/test/flow/prefix_and_tail.cpp
View file @
04e02412
...
@@ -20,31 +20,15 @@ using namespace caf;
...
@@ -20,31 +20,15 @@ using namespace caf;
namespace
{
namespace
{
template
<
class
T
>
struct
test_observer
{
void
on_next
(
T
x
)
{
values
.
emplace_back
(
std
::
move
(
x
));
}
void
on_error
(
const
error
&
what
)
{
had_error
=
true
;
err
=
what
;
}
void
on_complete
()
{
had_complete
=
true
;
}
std
::
vector
<
T
>
values
;
bool
had_error
=
false
;
bool
had_complete
=
false
;
error
err
;
};
struct
fixture
:
test_coordinator_fixture
<>
{
struct
fixture
:
test_coordinator_fixture
<>
{
flow
::
scoped_coordinator_ptr
ctx
=
flow
::
make_scoped_coordinator
();
flow
::
scoped_coordinator_ptr
ctx
=
flow
::
make_scoped_coordinator
();
};
};
template
<
class
T
,
class
...
Ts
>
auto
ls
(
T
x
,
Ts
...
xs
)
{
return
std
::
vector
<
T
>
{
x
,
xs
...};
}
}
// namespace
}
// namespace
BEGIN_FIXTURE_SCOPE
(
fixture
)
BEGIN_FIXTURE_SCOPE
(
fixture
)
...
@@ -54,84 +38,78 @@ SCENARIO("prefix_and_tail splits off initial elements") {
...
@@ -54,84 +38,78 @@ SCENARIO("prefix_and_tail splits off initial elements") {
GIVEN
(
"a generation with 0 values"
)
{
GIVEN
(
"a generation with 0 values"
)
{
WHEN
(
"calling prefix_and_tail(2)"
)
{
WHEN
(
"calling prefix_and_tail(2)"
)
{
THEN
(
"the observer of prefix_and_tail only receives on_complete"
)
{
THEN
(
"the observer of prefix_and_tail only receives on_complete"
)
{
auto
inputs
=
std
::
vector
<
int
>
{};
auto
snk
=
flow
::
make_auto_observer
<
tuple_t
>
();
auto
obs
=
std
::
make_shared
<
test_observer
<
tuple_t
>>
();
ctx
->
make_observable
()
ctx
->
make_observable
()
.
from_container
(
inputs
)
.
empty
<
int
>
()
//
.
prefix_and_tail
(
2
)
.
prefix_and_tail
(
2
)
.
subscribe
(
flow
::
make_observer_from_ptr
(
obs
));
.
subscribe
(
snk
->
as_observer
(
));
ctx
->
run
();
ctx
->
run
();
CHECK
(
obs
->
had_complete
);
CHECK
(
snk
->
buf
.
empty
()
);
CHECK
(
!
obs
->
had_error
);
CHECK
_EQ
(
snk
->
state
,
flow
::
observer_state
::
completed
);
CHECK
(
obs
->
values
.
empty
()
);
CHECK
_EQ
(
snk
->
err
,
error
{}
);
}
}
}
}
}
}
GIVEN
(
"a generation with 1 values"
)
{
GIVEN
(
"a generation with 1 values"
)
{
WHEN
(
"calling prefix_and_tail(2)"
)
{
WHEN
(
"calling prefix_and_tail(2)"
)
{
THEN
(
"the observer of prefix_and_tail only receives on_complete"
)
{
THEN
(
"the observer of prefix_and_tail only receives on_complete"
)
{
auto
inputs
=
std
::
vector
<
int
>
{
1
};
auto
snk
=
flow
::
make_auto_observer
<
tuple_t
>
();
auto
obs
=
std
::
make_shared
<
test_observer
<
tuple_t
>>
();
ctx
->
make_observable
()
ctx
->
make_observable
()
.
from_container
(
inputs
)
.
just
(
1
)
//
.
prefix_and_tail
(
2
)
.
prefix_and_tail
(
2
)
.
subscribe
(
flow
::
make_observer_from_ptr
(
obs
));
.
subscribe
(
snk
->
as_observer
(
));
ctx
->
run
();
ctx
->
run
();
CHECK
(
obs
->
had_complete
);
CHECK
(
snk
->
buf
.
empty
()
);
CHECK
(
!
obs
->
had_error
);
CHECK
_EQ
(
snk
->
state
,
flow
::
observer_state
::
completed
);
CHECK
(
obs
->
values
.
empty
()
);
CHECK
_EQ
(
snk
->
err
,
error
{}
);
}
}
}
}
}
}
GIVEN
(
"a generation with 2 values"
)
{
GIVEN
(
"a generation with 2 values"
)
{
WHEN
(
"calling prefix_and_tail(2)"
)
{
WHEN
(
"calling prefix_and_tail(2)"
)
{
THEN
(
"the observer receives the first 2 elements plus empty remainder"
)
{
THEN
(
"the observer receives the first 2 elements plus empty remainder"
)
{
auto
inputs
=
std
::
vector
<
int
>
{
1
,
2
};
auto
snk
=
flow
::
make_auto_observer
<
int
>
();
auto
prefix_vals
=
std
::
vector
<
int
>
{
1
,
2
};
auto
tail_vals
=
std
::
vector
<
int
>
{};
auto
obs
=
std
::
make_shared
<
test_observer
<
int
>>
();
auto
flat_map_calls
=
0
;
auto
flat_map_calls
=
0
;
ctx
->
make_observable
()
ctx
->
make_observable
()
.
from_container
(
inputs
)
.
iota
(
1
)
.
take
(
2
)
.
prefix_and_tail
(
2
)
.
prefix_and_tail
(
2
)
.
flat_map
([
&
](
const
tuple_t
&
x
)
{
.
flat_map
([
&
](
const
tuple_t
&
x
)
{
++
flat_map_calls
;
++
flat_map_calls
;
auto
&
[
prefix
,
tail
]
=
x
.
data
();
auto
&
[
prefix
,
tail
]
=
x
.
data
();
CHECK_EQ
(
prefix
,
prefix_vals
);
CHECK_EQ
(
prefix
,
ls
(
1
,
2
)
);
return
tail
;
return
tail
;
})
})
.
subscribe
(
flow
::
make_observer_from_ptr
(
obs
));
.
subscribe
(
snk
->
as_observer
(
));
ctx
->
run
();
ctx
->
run
();
CHECK
(
snk
->
buf
.
empty
());
CHECK_EQ
(
flat_map_calls
,
1
);
CHECK_EQ
(
flat_map_calls
,
1
);
CHECK_EQ
(
obs
->
values
,
tail_vals
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
completed
);
CHECK
(
obs
->
had_complete
);
CHECK_EQ
(
snk
->
err
,
error
{});
CHECK
(
!
obs
->
had_error
);
}
}
}
}
}
}
GIVEN
(
"a generation with 8 values"
)
{
GIVEN
(
"a generation with 8 values"
)
{
WHEN
(
"calling prefix_and_tail(2)"
)
{
WHEN
(
"calling prefix_and_tail(2)"
)
{
THEN
(
"the observer receives the first 2 elements plus remainder"
)
{
THEN
(
"the observer receives the first 2 elements plus remainder"
)
{
auto
inputs
=
std
::
vector
<
int
>
{
1
,
2
,
4
,
8
,
16
,
32
,
64
,
128
};
auto
snk
=
flow
::
make_auto_observer
<
int
>
();
auto
prefix_vals
=
std
::
vector
<
int
>
{
1
,
2
};
auto
tail_vals
=
std
::
vector
<
int
>
{
4
,
8
,
16
,
32
,
64
,
128
};
auto
obs
=
std
::
make_shared
<
test_observer
<
int
>>
();
auto
flat_map_calls
=
0
;
auto
flat_map_calls
=
0
;
ctx
->
make_observable
()
ctx
->
make_observable
()
.
from_container
(
inputs
)
.
iota
(
1
)
.
take
(
8
)
.
prefix_and_tail
(
2
)
.
prefix_and_tail
(
2
)
.
flat_map
([
&
](
const
tuple_t
&
x
)
{
.
flat_map
([
&
](
const
tuple_t
&
x
)
{
++
flat_map_calls
;
++
flat_map_calls
;
auto
&
[
prefix
,
tail
]
=
x
.
data
();
auto
&
[
prefix
,
tail
]
=
x
.
data
();
CHECK_EQ
(
prefix
,
prefix_vals
);
CHECK_EQ
(
prefix
,
ls
(
1
,
2
)
);
return
tail
;
return
tail
;
})
})
.
subscribe
(
flow
::
make_observer_from_ptr
(
obs
));
.
subscribe
(
snk
->
as_observer
(
));
ctx
->
run
();
ctx
->
run
();
CHECK_EQ
(
flat_map_calls
,
1
);
CHECK_EQ
(
flat_map_calls
,
1
);
CHECK_EQ
(
obs
->
values
,
tail_vals
);
CHECK_EQ
(
snk
->
buf
,
ls
(
3
,
4
,
5
,
6
,
7
,
8
)
);
CHECK
(
obs
->
had_complete
);
CHECK
_EQ
(
snk
->
state
,
flow
::
observer_state
::
completed
);
CHECK
(
!
obs
->
had_error
);
CHECK
_EQ
(
snk
->
err
,
error
{}
);
}
}
}
}
}
}
...
@@ -142,69 +120,62 @@ SCENARIO("head_and_tail splits off the first element") {
...
@@ -142,69 +120,62 @@ SCENARIO("head_and_tail splits off the first element") {
GIVEN
(
"a generation with 0 values"
)
{
GIVEN
(
"a generation with 0 values"
)
{
WHEN
(
"calling head_and_tail"
)
{
WHEN
(
"calling head_and_tail"
)
{
THEN
(
"the observer of head_and_tail only receives on_complete"
)
{
THEN
(
"the observer of head_and_tail only receives on_complete"
)
{
auto
inputs
=
std
::
vector
<
int
>
{};
auto
snk
=
flow
::
make_auto_observer
<
tuple_t
>
();
auto
obs
=
std
::
make_shared
<
test_observer
<
tuple_t
>>
();
ctx
->
make_observable
()
ctx
//
.
empty
<
int
>
()
//
->
make_observable
()
.
from_container
(
inputs
)
.
head_and_tail
()
.
head_and_tail
()
.
subscribe
(
flow
::
make_observer_from_ptr
(
obs
));
.
subscribe
(
snk
->
as_observer
(
));
ctx
->
run
();
ctx
->
run
();
CHECK
(
obs
->
had_complete
);
CHECK
(
snk
->
buf
.
empty
()
);
CHECK
(
!
obs
->
had_error
);
CHECK
_EQ
(
snk
->
state
,
flow
::
observer_state
::
completed
);
CHECK
(
obs
->
values
.
empty
()
);
CHECK
_EQ
(
snk
->
err
,
error
{}
);
}
}
}
}
}
}
GIVEN
(
"a generation with 1 values"
)
{
GIVEN
(
"a generation with 1 values"
)
{
WHEN
(
"calling head_and_tail()"
)
{
WHEN
(
"calling head_and_tail()"
)
{
THEN
(
"the observer receives the first element plus empty remainder"
)
{
THEN
(
"the observer receives the first element plus empty remainder"
)
{
auto
inputs
=
std
::
vector
<
int
>
{
1
};
auto
snk
=
flow
::
make_auto_observer
<
int
>
();
auto
prefix_val
=
1
;
auto
tail_vals
=
std
::
vector
<
int
>
{};
auto
obs
=
std
::
make_shared
<
test_observer
<
int
>>
();
auto
flat_map_calls
=
0
;
auto
flat_map_calls
=
0
;
ctx
->
make_observable
()
ctx
->
make_observable
()
.
from_container
(
inputs
)
.
just
(
1
)
.
head_and_tail
()
.
head_and_tail
()
.
flat_map
([
&
](
const
tuple_t
&
x
)
{
.
flat_map
([
&
](
const
tuple_t
&
x
)
{
++
flat_map_calls
;
++
flat_map_calls
;
auto
&
[
prefix
,
tail
]
=
x
.
data
();
auto
&
[
prefix
,
tail
]
=
x
.
data
();
CHECK_EQ
(
prefix
,
prefix_val
);
CHECK_EQ
(
prefix
,
1
);
return
tail
;
return
tail
;
})
})
.
subscribe
(
flow
::
make_observer_from_ptr
(
obs
));
.
subscribe
(
snk
->
as_observer
(
));
ctx
->
run
();
ctx
->
run
();
CHECK
(
snk
->
buf
.
empty
());
CHECK_EQ
(
flat_map_calls
,
1
);
CHECK_EQ
(
flat_map_calls
,
1
);
CHECK_EQ
(
obs
->
values
,
tail_vals
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
completed
);
CHECK
(
obs
->
had_complete
);
CHECK_EQ
(
snk
->
err
,
error
{});
CHECK
(
!
obs
->
had_error
);
}
}
}
}
}
}
GIVEN
(
"a generation with 2 values"
)
{
GIVEN
(
"a generation with 2 values"
)
{
WHEN
(
"calling head_and_tail()"
)
{
WHEN
(
"calling head_and_tail()"
)
{
THEN
(
"the observer receives the first element plus remainder"
)
{
THEN
(
"the observer receives the first element plus remainder"
)
{
auto
inputs
=
std
::
vector
<
int
>
{
1
,
2
};
auto
snk
=
flow
::
make_auto_observer
<
int
>
();
auto
prefix_val
=
1
;
auto
tail_vals
=
std
::
vector
<
int
>
{
2
};
auto
obs
=
std
::
make_shared
<
test_observer
<
int
>>
();
auto
flat_map_calls
=
0
;
auto
flat_map_calls
=
0
;
ctx
->
make_observable
()
ctx
->
make_observable
()
.
from_container
(
inputs
)
.
iota
(
1
)
.
take
(
2
)
.
head_and_tail
()
.
head_and_tail
()
.
flat_map
([
&
](
const
tuple_t
&
x
)
{
.
flat_map
([
&
](
const
tuple_t
&
x
)
{
++
flat_map_calls
;
++
flat_map_calls
;
auto
&
[
prefix
,
tail
]
=
x
.
data
();
auto
&
[
prefix
,
tail
]
=
x
.
data
();
CHECK_EQ
(
prefix
,
prefix_val
);
CHECK_EQ
(
prefix
,
1
);
return
tail
;
return
tail
;
})
})
.
subscribe
(
flow
::
make_observer_from_ptr
(
obs
));
.
subscribe
(
snk
->
as_observer
(
));
ctx
->
run
();
ctx
->
run
();
CHECK_EQ
(
flat_map_calls
,
1
);
CHECK_EQ
(
flat_map_calls
,
1
);
CHECK_EQ
(
obs
->
values
,
tail_vals
);
CHECK_EQ
(
snk
->
buf
,
ls
(
2
)
);
CHECK
(
obs
->
had_complete
);
CHECK
_EQ
(
snk
->
state
,
flow
::
observer_state
::
completed
);
CHECK
(
!
obs
->
had_error
);
CHECK
_EQ
(
snk
->
err
,
error
{}
);
}
}
}
}
}
}
...
...
libcaf_core/test/flow/single.cpp
View file @
04e02412
...
@@ -8,6 +8,7 @@
...
@@ -8,6 +8,7 @@
#include "core-test.hpp"
#include "core-test.hpp"
#include "caf/flow/op/cell.hpp"
#include "caf/flow/scoped_coordinator.hpp"
#include "caf/flow/scoped_coordinator.hpp"
using
namespace
caf
;
using
namespace
caf
;
...
@@ -28,21 +29,23 @@ SCENARIO("singles emit at most one value") {
...
@@ -28,21 +29,23 @@ SCENARIO("singles emit at most one value") {
WHEN
(
"an observer subscribes before the values has been set"
)
{
WHEN
(
"an observer subscribes before the values has been set"
)
{
THEN
(
"the observer receives the value when calling set_value"
)
{
THEN
(
"the observer receives the value when calling set_value"
)
{
auto
outputs
=
i32_list
{};
auto
outputs
=
i32_list
{};
auto
single_int
=
flow
::
make_single
<
int32_t
>
(
ctx
.
get
());
auto
cell
=
make_counted
<
flow
::
op
::
cell
<
int32_t
>>
(
ctx
.
get
());
auto
single_int
=
flow
::
single
<
int32_t
>
{
cell
};
single_int
//
single_int
//
.
as_observable
()
.
as_observable
()
.
for_each
([
&
outputs
](
int32_t
x
)
{
outputs
.
emplace_back
(
x
);
});
.
for_each
([
&
outputs
](
int32_t
x
)
{
outputs
.
emplace_back
(
x
);
});
ctx
->
run
();
ctx
->
run
();
CHECK_EQ
(
outputs
,
i32_list
());
CHECK_EQ
(
outputs
,
i32_list
());
single_int
.
set_value
(
42
);
cell
->
set_value
(
42
);
CHECK_EQ
(
outputs
,
i32_list
({
42
}));
CHECK_EQ
(
outputs
,
i32_list
({
42
}));
}
}
}
}
WHEN
(
"an observer subscribes after the values has been set"
)
{
WHEN
(
"an observer subscribes after the values has been set"
)
{
THEN
(
"the observer receives the value immediately"
)
{
THEN
(
"the observer receives the value immediately"
)
{
auto
outputs
=
i32_list
{};
auto
outputs
=
i32_list
{};
auto
single_int
=
flow
::
make_single
<
int32_t
>
(
ctx
.
get
());
auto
cell
=
make_counted
<
flow
::
op
::
cell
<
int32_t
>>
(
ctx
.
get
());
single_int
.
set_value
(
42
);
auto
single_int
=
flow
::
single
<
int32_t
>
{
cell
};
cell
->
set_value
(
42
);
single_int
//
single_int
//
.
as_observable
()
.
as_observable
()
.
for_each
([
&
outputs
](
int32_t
x
)
{
outputs
.
emplace_back
(
x
);
});
.
for_each
([
&
outputs
](
int32_t
x
)
{
outputs
.
emplace_back
(
x
);
});
...
...
libcaf_core/test/flow/zip_with.cpp
View file @
04e02412
...
@@ -4,11 +4,10 @@
...
@@ -4,11 +4,10 @@
#define CAF_SUITE flow.zip_with
#define CAF_SUITE flow.zip_with
#include "caf/flow/
zip_with
.hpp"
#include "caf/flow/
observable_builder
.hpp"
#include "core-test.hpp"
#include "core-test.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/scoped_coordinator.hpp"
#include "caf/flow/scoped_coordinator.hpp"
using
namespace
caf
;
using
namespace
caf
;
...
@@ -27,24 +26,23 @@ SCENARIO("zip_with combines inputs") {
...
@@ -27,24 +26,23 @@ SCENARIO("zip_with combines inputs") {
GIVEN
(
"two observables"
)
{
GIVEN
(
"two observables"
)
{
WHEN
(
"merging them with zip_with"
)
{
WHEN
(
"merging them with zip_with"
)
{
THEN
(
"the observer receives the combined output of both sources"
)
{
THEN
(
"the observer receives the combined output of both sources"
)
{
auto
o1
=
flow
::
make_passive_observer
<
int
>
();
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
auto
fn
=
[](
int
x
,
int
y
)
{
return
x
+
y
;
};
ctx
->
make_observable
()
auto
r1
=
ctx
->
make_observable
().
repeat
(
11
).
take
(
113
);
.
zip_with
([](
int
x
,
int
y
)
{
return
x
+
y
;
},
auto
r2
=
ctx
->
make_observable
().
repeat
(
22
).
take
(
223
);
ctx
->
make_observable
().
repeat
(
11
).
take
(
113
),
flow
::
zip_with
(
fn
,
std
::
move
(
r1
),
std
::
move
(
r2
))
ctx
->
make_observable
().
repeat
(
22
).
take
(
223
))
.
subscribe
(
o1
->
as_observer
());
.
subscribe
(
snk
->
as_observer
());
ctx
->
run
();
ctx
->
run
();
REQUIRE_EQ
(
o1
->
state
,
flow
::
observer_state
::
subscribed
);
REQUIRE_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
o1
->
sub
.
request
(
64
);
snk
->
sub
.
request
(
64
);
ctx
->
run
();
ctx
->
run
();
CHECK_EQ
(
o1
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
o1
->
buf
.
size
(),
64u
);
CHECK_EQ
(
snk
->
buf
.
size
(),
64u
);
o1
->
sub
.
request
(
64
);
snk
->
sub
.
request
(
64
);
ctx
->
run
();
ctx
->
run
();
CHECK_EQ
(
o1
->
state
,
flow
::
observer_state
::
completed
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
completed
);
CHECK_EQ
(
o1
->
buf
.
size
(),
113u
);
CHECK_EQ
(
snk
->
buf
.
size
(),
113u
);
CHECK
(
std
::
all_of
(
o1
->
buf
.
begin
(),
o1
->
buf
.
begin
(),
CHECK_EQ
(
snk
->
buf
,
std
::
vector
<
int
>
(
113
,
33
));
[](
int
x
)
{
return
x
==
33
;
}));
}
}
}
}
}
}
...
@@ -54,54 +52,16 @@ SCENARIO("zip_with emits nothing when zipping an empty observable") {
...
@@ -54,54 +52,16 @@ SCENARIO("zip_with emits nothing when zipping an empty observable") {
GIVEN
(
"two observables, one of them empty"
)
{
GIVEN
(
"two observables, one of them empty"
)
{
WHEN
(
"merging them with zip_with"
)
{
WHEN
(
"merging them with zip_with"
)
{
THEN
(
"the observer sees on_complete immediately"
)
{
THEN
(
"the observer sees on_complete immediately"
)
{
auto
o1
=
flow
::
make_passive_observer
<
int
>
();
auto
snk
=
flow
::
make_auto_observer
<
int
>
();
auto
fn
=
[](
int
x
,
int
y
,
int
z
)
{
return
x
+
y
+
z
;
};
ctx
->
make_observable
()
auto
r1
=
ctx
->
make_observable
().
repeat
(
11
);
.
zip_with
([](
int
x
,
int
y
,
int
z
)
{
return
x
+
y
+
z
;
},
auto
r2
=
ctx
->
make_observable
().
repeat
(
22
);
ctx
->
make_observable
().
repeat
(
11
),
auto
r3
=
ctx
->
make_observable
().
empty
<
int
>
();
ctx
->
make_observable
().
repeat
(
22
),
flow
::
zip_with
(
fn
,
std
::
move
(
r1
),
std
::
move
(
r2
),
std
::
move
(
r3
))
ctx
->
make_observable
().
empty
<
int
>
())
.
subscribe
(
o1
->
as_observer
());
.
subscribe
(
snk
->
as_observer
());
ctx
->
run
();
REQUIRE_EQ
(
o1
->
state
,
flow
::
observer_state
::
subscribed
);
o1
->
sub
.
request
(
64
);
ctx
->
run
();
CHECK_EQ
(
o1
->
state
,
flow
::
observer_state
::
completed
);
CHECK
(
o1
->
buf
.
empty
());
}
}
}
}
SCENARIO
(
"zip_with may only have more than one subscriber"
)
{
GIVEN
(
"two observables"
)
{
WHEN
(
"merging them with zip_with"
)
{
THEN
(
"all observer receives the combined output of both sources"
)
{
auto
o1
=
flow
::
make_passive_observer
<
int
>
();
auto
o2
=
flow
::
make_passive_observer
<
int
>
();
auto
fn
=
[](
int
x
,
int
y
)
{
return
x
+
y
;
};
auto
r1
=
ctx
->
make_observable
().
repeat
(
11
).
take
(
113
);
auto
r2
=
ctx
->
make_observable
().
repeat
(
22
).
take
(
223
);
auto
zip
=
flow
::
zip_with
(
fn
,
std
::
move
(
r1
),
std
::
move
(
r2
));
zip
.
subscribe
(
o1
->
as_observer
());
zip
.
subscribe
(
o2
->
as_observer
());
ctx
->
run
();
REQUIRE_EQ
(
o1
->
state
,
flow
::
observer_state
::
subscribed
);
o1
->
sub
.
request
(
64
);
o2
->
sub
.
request
(
64
);
ctx
->
run
();
CHECK_EQ
(
o1
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
o1
->
buf
.
size
(),
64u
);
CHECK_EQ
(
o2
->
buf
.
size
(),
64u
);
o1
->
sub
.
request
(
64
);
o2
->
dispose
();
ctx
->
run
();
ctx
->
run
();
CHECK_EQ
(
o1
->
state
,
flow
::
observer_state
::
completed
);
CHECK
(
snk
->
buf
.
empty
());
CHECK_EQ
(
o1
->
buf
.
size
(),
113u
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
completed
);
CHECK
(
std
::
all_of
(
o1
->
buf
.
begin
(),
o1
->
buf
.
begin
(),
[](
int
x
)
{
return
x
==
33
;
}));
CHECK_EQ
(
o2
->
buf
.
size
(),
64u
);
CHECK
(
std
::
all_of
(
o2
->
buf
.
begin
(),
o2
->
buf
.
begin
(),
[](
int
x
)
{
return
x
==
33
;
}));
}
}
}
}
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment