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
4c68deb8
Commit
4c68deb8
authored
May 06, 2021
by
Dominik Charousset
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add infrastructure for a new flow API
parent
b10ee098
Changes
54
Hide whitespace changes
Inline
Side-by-side
Showing
54 changed files
with
6105 additions
and
31 deletions
+6105
-31
libcaf_core/CMakeLists.txt
libcaf_core/CMakeLists.txt
+16
-1
libcaf_core/caf/actor_system.hpp
libcaf_core/caf/actor_system.hpp
+55
-0
libcaf_core/caf/async/batch.hpp
libcaf_core/caf/async/batch.hpp
+229
-0
libcaf_core/caf/async/blocking_observer.hpp
libcaf_core/caf/async/blocking_observer.hpp
+69
-0
libcaf_core/caf/async/bounded_buffer.hpp
libcaf_core/caf/async/bounded_buffer.hpp
+417
-0
libcaf_core/caf/async/consumer.hpp
libcaf_core/caf/async/consumer.hpp
+45
-0
libcaf_core/caf/async/fwd.hpp
libcaf_core/caf/async/fwd.hpp
+33
-0
libcaf_core/caf/async/observer_buffer.hpp
libcaf_core/caf/async/observer_buffer.hpp
+178
-0
libcaf_core/caf/async/producer.hpp
libcaf_core/caf/async/producer.hpp
+47
-0
libcaf_core/caf/callback.hpp
libcaf_core/caf/callback.hpp
+7
-0
libcaf_core/caf/defaults.hpp
libcaf_core/caf/defaults.hpp
+8
-0
libcaf_core/caf/disposable.hpp
libcaf_core/caf/disposable.hpp
+23
-13
libcaf_core/caf/event_based_actor.hpp
libcaf_core/caf/event_based_actor.hpp
+2
-0
libcaf_core/caf/flow/concat.hpp
libcaf_core/caf/flow/concat.hpp
+28
-0
libcaf_core/caf/flow/coordinator.hpp
libcaf_core/caf/flow/coordinator.hpp
+102
-0
libcaf_core/caf/flow/fwd.hpp
libcaf_core/caf/flow/fwd.hpp
+146
-0
libcaf_core/caf/flow/merge.hpp
libcaf_core/caf/flow/merge.hpp
+27
-0
libcaf_core/caf/flow/observable.hpp
libcaf_core/caf/flow/observable.hpp
+1638
-0
libcaf_core/caf/flow/observable_builder.hpp
libcaf_core/caf/flow/observable_builder.hpp
+270
-0
libcaf_core/caf/flow/observer.hpp
libcaf_core/caf/flow/observer.hpp
+438
-0
libcaf_core/caf/flow/scoped_coordinator.hpp
libcaf_core/caf/flow/scoped_coordinator.hpp
+59
-0
libcaf_core/caf/flow/single.hpp
libcaf_core/caf/flow/single.hpp
+229
-0
libcaf_core/caf/flow/step.hpp
libcaf_core/caf/flow/step.hpp
+191
-0
libcaf_core/caf/flow/subscription.hpp
libcaf_core/caf/flow/subscription.hpp
+118
-0
libcaf_core/caf/fwd.hpp
libcaf_core/caf/fwd.hpp
+4
-0
libcaf_core/caf/intrusive_ptr.hpp
libcaf_core/caf/intrusive_ptr.hpp
+19
-0
libcaf_core/caf/ref_counted.hpp
libcaf_core/caf/ref_counted.hpp
+8
-10
libcaf_core/caf/response_handle.hpp
libcaf_core/caf/response_handle.hpp
+13
-0
libcaf_core/caf/scheduled_actor.hpp
libcaf_core/caf/scheduled_actor.hpp
+55
-2
libcaf_core/caf/scheduled_actor/flow.hpp
libcaf_core/caf/scheduled_actor/flow.hpp
+45
-0
libcaf_core/caf/sec.hpp
libcaf_core/caf/sec.hpp
+5
-0
libcaf_core/caf/type_id.hpp
libcaf_core/caf/type_id.hpp
+1
-0
libcaf_core/src/async/batch.cpp
libcaf_core/src/async/batch.cpp
+97
-0
libcaf_core/src/async/consumer.cpp
libcaf_core/src/async/consumer.cpp
+13
-0
libcaf_core/src/async/producer.cpp
libcaf_core/src/async/producer.cpp
+13
-0
libcaf_core/src/flow/coordinator.cpp
libcaf_core/src/flow/coordinator.cpp
+39
-0
libcaf_core/src/flow/scoped_coordinator.cpp
libcaf_core/src/flow/scoped_coordinator.cpp
+65
-0
libcaf_core/src/flow/subscription.cpp
libcaf_core/src/flow/subscription.cpp
+17
-0
libcaf_core/src/init_global_meta_objects.cpp
libcaf_core/src/init_global_meta_objects.cpp
+1
-0
libcaf_core/src/scheduled_actor.cpp
libcaf_core/src/scheduled_actor.cpp
+67
-3
libcaf_core/src/sec_strings.cpp
libcaf_core/src/sec_strings.cpp
+461
-0
libcaf_core/test/async/blocking_for_each.cpp
libcaf_core/test/async/blocking_for_each.cpp
+51
-0
libcaf_core/test/async/bounded_buffer.cpp
libcaf_core/test/async/bounded_buffer.cpp
+50
-0
libcaf_core/test/async/publishing_queue.cpp
libcaf_core/test/async/publishing_queue.cpp
+54
-0
libcaf_core/test/detail/group_tunnel.cpp
libcaf_core/test/detail/group_tunnel.cpp
+1
-2
libcaf_core/test/flow/concat.cpp
libcaf_core/test/flow/concat.cpp
+48
-0
libcaf_core/test/flow/concat_map.cpp
libcaf_core/test/flow/concat_map.cpp
+94
-0
libcaf_core/test/flow/flat_map.cpp
libcaf_core/test/flow/flat_map.cpp
+96
-0
libcaf_core/test/flow/for_each.cpp
libcaf_core/test/flow/for_each.cpp
+117
-0
libcaf_core/test/flow/merge.cpp
libcaf_core/test/flow/merge.cpp
+49
-0
libcaf_core/test/flow/observe_on.cpp
libcaf_core/test/flow/observe_on.cpp
+46
-0
libcaf_core/test/flow/single.cpp
libcaf_core/test/flow/single.cpp
+56
-0
libcaf_core/test/response_handle.cpp
libcaf_core/test/response_handle.cpp
+137
-0
libcaf_test/caf/test/bdd_dsl.hpp
libcaf_test/caf/test/bdd_dsl.hpp
+8
-0
No files found.
libcaf_core/CMakeLists.txt
View file @
4c68deb8
...
...
@@ -42,7 +42,7 @@ configure_file("${PROJECT_SOURCE_DIR}/cmake/build_config.hpp.in"
install
(
FILES
"
${
CMAKE_CURRENT_BINARY_DIR
}
/caf/detail/build_config.hpp"
DESTINATION
"
${
CMAKE_INSTALL_INCLUDEDIR
}
/caf/detail/"
)
#
-- add targets
---------------------------------------------------------------
#
-- add targets -
---------------------------------------------------------------
caf_add_component
(
core
...
...
@@ -81,6 +81,9 @@ caf_add_component(
src/actor_registry.cpp
src/actor_system.cpp
src/actor_system_config.cpp
src/async/batch.cpp
src/async/consumer.cpp
src/async/producer.cpp
src/attachable.cpp
src/behavior.cpp
src/binary_deserializer.cpp
...
...
@@ -141,6 +144,9 @@ caf_add_component(
src/error.cpp
src/event_based_actor.cpp
src/execution_unit.cpp
src/flow/coordinator.cpp
src/flow/scoped_coordinator.cpp
src/flow/subscription.cpp
src/forwarding_actor_proxy.cpp
src/group.cpp
src/group_manager.cpp
...
...
@@ -221,6 +227,7 @@ caf_add_component(
actor_system_config
actor_termination
aout
async.bounded_buffer
behavior
binary_deserializer
binary_serializer
...
...
@@ -273,6 +280,13 @@ caf_add_component(
dynamic_spawn
error
expected
flow.concat
flow.concat_map
flow.flat_map
flow.for_each
flow.merge
flow.observe_on
flow.single
function_view
fused_downstream_manager
handles
...
...
@@ -315,6 +329,7 @@ caf_add_component(
policy.select_all
policy.select_any
request_timeout
response_handle
response_promise
result
save_inspector
...
...
libcaf_core/caf/actor_system.hpp
View file @
4c68deb8
...
...
@@ -587,6 +587,61 @@ public:
return
res
;
}
/// Creates a new, cooperatively scheduled `flow::coordinator`. The returned
/// coordinator is constructed but has not been added to the scheduler yet to
/// allow the caller to set up flows.
/// @returns A pointer to the new coordinator and a function object that the
/// caller must invoke to launch the coordinator. After the
/// coordinator started running, the caller *must not* access the
/// pointer again.
template
<
class
Impl
,
spawn_options
=
no_spawn_options
,
class
...
Ts
>
auto
make_flow_coordinator
(
Ts
&&
...
xs
)
{
static_assert
(
std
::
is_base_of_v
<
scheduled_actor
,
Impl
>
,
"make_flow_coordinator only supports scheduled actors ATM"
);
CAF_SET_LOGGER_SYS
(
this
);
actor_config
cfg
{
dummy_execution_unit
(),
nullptr
};
auto
res
=
make_actor
<
Impl
>
(
next_actor_id
(),
node
(),
this
,
cfg
,
std
::
forward
<
Ts
>
(
xs
)...);
auto
ptr
=
static_cast
<
Impl
*>
(
actor_cast
<
abstract_actor
*>
(
res
));
#ifdef CAF_ENABLE_ACTOR_PROFILER
profiler_add_actor
(
*
ptr
,
cfg
.
parent
);
#endif
auto
launch
=
[
res
,
host
{
cfg
.
host
}]
{
// Note: we pass `res` to this lambda instead of `ptr` to keep a strong
// reference to the actor.
static_cast
<
Impl
*>
(
actor_cast
<
abstract_actor
*>
(
res
))
->
launch
(
host
,
false
,
false
);
};
return
std
::
make_tuple
(
ptr
,
launch
);
}
/// Creates a new, cooperatively scheduled actor. The returned actor is
/// constructed but has not been added to the scheduler yet to allow the
/// caller to set up any additional logic on the actor before it starts.
/// @returns A pointer to the new actor and a function object that the caller
/// must invoke to launch the actor. After the actor started running,
/// the caller *must not* access the pointer again.
template
<
class
Impl
,
spawn_options
=
no_spawn_options
,
class
...
Ts
>
auto
spawn_inactive
(
Ts
&&
...
xs
)
{
static_assert
(
std
::
is_base_of_v
<
scheduled_actor
,
Impl
>
,
"only scheduled actors may get spawned inactively"
);
CAF_SET_LOGGER_SYS
(
this
);
actor_config
cfg
{
dummy_execution_unit
(),
nullptr
};
auto
res
=
make_actor
<
Impl
>
(
next_actor_id
(),
node
(),
this
,
cfg
,
std
::
forward
<
Ts
>
(
xs
)...);
auto
ptr
=
static_cast
<
Impl
*>
(
actor_cast
<
abstract_actor
*>
(
res
));
#ifdef CAF_ENABLE_ACTOR_PROFILER
profiler_add_actor
(
*
ptr
,
cfg
.
parent
);
#endif
auto
launch
=
[
res
,
host
{
cfg
.
host
}]
{
// Note: we pass `res` to this lambda instead of `ptr` to keep a strong
// reference to the actor.
static_cast
<
Impl
*>
(
actor_cast
<
abstract_actor
*>
(
res
))
->
launch
(
host
,
false
,
false
);
};
return
std
::
make_tuple
(
ptr
,
launch
);
}
void
profiler_add_actor
(
const
local_actor
&
self
,
const
local_actor
*
parent
)
{
if
(
profiler_
)
profiler_
->
add_actor
(
self
,
parent
);
...
...
libcaf_core/caf/async/batch.hpp
0 → 100644
View file @
4c68deb8
// 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 <cstdint>
#include <cstdlib>
#include <memory>
#include "caf/async/fwd.hpp"
#include "caf/byte.hpp"
#include "caf/config.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/fwd.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/raise_error.hpp"
#include "caf/span.hpp"
#include "caf/type_id.hpp"
#ifdef CAF_CLANG
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wc99-extensions"
#elif defined(CAF_GCC)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wpedantic"
#elif defined(CAF_MSVC)
# pragma warning(push)
# pragma warning(disable : 4200)
#endif
namespace
caf
::
async
{
/// A reference-counted container for transferring items from publishers to
/// subscribers.
class
CAF_CORE_EXPORT
batch
{
public:
template
<
class
T
>
friend
batch
make_batch
(
span
<
const
T
>
items
);
using
item_destructor
=
void
(
*
)(
type_id_t
,
uint16_t
,
size_t
,
byte
*
);
batch
()
=
default
;
batch
(
batch
&&
)
=
default
;
batch
(
const
batch
&
)
=
default
;
batch
&
operator
=
(
batch
&&
)
=
default
;
batch
&
operator
=
(
const
batch
&
)
=
default
;
size_t
size
()
const
noexcept
{
return
data_
?
data_
->
size
()
:
0
;
}
bool
empty
()
const
noexcept
{
return
data_
?
data_
->
size
()
==
0
:
true
;
}
template
<
class
T
>
span
<
T
>
items
()
noexcept
{
return
data_
?
data_
->
items
<
T
>
()
:
span
<
T
>
{};
}
template
<
class
T
>
span
<
const
T
>
items
()
const
noexcept
{
return
data_
?
data_
->
items
<
T
>
()
:
span
<
const
T
>
{};
}
bool
save
(
serializer
&
f
)
const
;
bool
save
(
binary_serializer
&
f
)
const
;
bool
load
(
deserializer
&
f
);
bool
load
(
binary_deserializer
&
f
);
void
swap
(
batch
&
other
)
{
data_
.
swap
(
other
.
data_
);
}
private:
template
<
class
Inspector
>
bool
save_impl
(
Inspector
&
f
)
const
;
template
<
class
Inspector
>
bool
load_impl
(
Inspector
&
f
);
class
data
{
public:
data
()
=
delete
;
data
(
const
data
&
)
=
delete
;
data
&
operator
=
(
const
data
&
)
=
delete
;
data
(
item_destructor
destroy_items
,
type_id_t
item_type
,
uint16_t
item_size
,
size_t
size
)
:
rc_
(
1
),
destroy_items_
(
destroy_items
),
item_type_
(
item_type
),
item_size_
(
item_size
),
size_
(
size
)
{
// nop
}
~
data
()
{
if
(
size_
>
0
)
destroy_items_
(
item_type_
,
item_size_
,
size_
,
storage_
);
}
// -- reference counting ---------------------------------------------------
bool
unique
()
const
noexcept
{
return
rc_
==
1
;
}
void
ref
()
const
noexcept
{
rc_
.
fetch_add
(
1
,
std
::
memory_order_relaxed
);
}
void
deref
()
noexcept
{
if
(
unique
()
||
rc_
.
fetch_sub
(
1
,
std
::
memory_order_acq_rel
)
==
1
)
{
this
->~
data
();
free
(
this
);
}
}
friend
void
intrusive_ptr_add_ref
(
const
data
*
ptr
)
{
ptr
->
ref
();
}
friend
void
intrusive_ptr_release
(
data
*
ptr
)
{
ptr
->
deref
();
}
// -- properties -----------------------------------------------------------
type_id_t
item_type
()
{
return
item_type_
;
}
size_t
size
()
noexcept
{
return
size_
;
}
byte
*
storage
()
noexcept
{
return
storage_
;
}
const
byte
*
storage
()
const
noexcept
{
return
storage_
;
}
template
<
class
T
>
span
<
T
>
items
()
noexcept
{
return
{
reinterpret_cast
<
T
*>
(
storage_
),
size_
};
}
template
<
class
T
>
span
<
const
T
>
items
()
const
noexcept
{
return
{
reinterpret_cast
<
const
T
*>
(
storage_
),
size_
};
}
// -- serialization --------------------------------------------------------
/// @pre `size() > 0`
template
<
class
Inspector
>
bool
save
(
Inspector
&
sink
)
const
;
template
<
class
Inspector
>
bool
load
(
Inspector
&
source
);
private:
mutable
std
::
atomic
<
size_t
>
rc_
;
item_destructor
destroy_items_
;
type_id_t
item_type_
;
uint16_t
item_size_
;
size_t
size_
;
byte
storage_
[];
};
explicit
batch
(
intrusive_ptr
<
data
>
ptr
)
:
data_
(
std
::
move
(
ptr
))
{
// nop
}
intrusive_ptr
<
data
>
data_
;
};
template
<
class
Inspector
>
auto
inspect
(
Inspector
&
f
,
batch
&
x
)
->
std
::
enable_if_t
<!
Inspector
::
is_loading
,
decltype
(
x
.
save
(
f
))
>
{
return
x
.
save
(
f
);
}
template
<
class
Inspector
>
auto
inspect
(
Inspector
&
f
,
batch
&
x
)
->
std
::
enable_if_t
<
Inspector
::
is_loading
,
decltype
(
x
.
load
(
f
))
>
{
return
x
.
load
(
f
);
}
template
<
class
T
>
batch
make_batch
(
span
<
const
T
>
items
)
{
static_assert
(
sizeof
(
T
)
<
0xFFFF
);
auto
total_size
=
sizeof
(
batch
::
data
)
+
(
items
.
size
()
*
sizeof
(
T
));
auto
vptr
=
malloc
(
total_size
);
if
(
vptr
==
nullptr
)
CAF_RAISE_ERROR
(
std
::
bad_alloc
,
"make_batch"
);
auto
destroy_items
=
[](
type_id_t
,
uint16_t
,
size_t
size
,
byte
*
storage
)
{
auto
ptr
=
reinterpret_cast
<
T
*>
(
storage
);
std
::
destroy
(
ptr
,
ptr
+
size
);
};
intrusive_ptr
<
batch
::
data
>
ptr
{
new
(
vptr
)
batch
::
data
(
destroy_items
,
type_id_or_invalid
<
T
>
(),
static_cast
<
uint16_t
>
(
sizeof
(
T
)),
items
.
size
()),
false
};
std
::
uninitialized_copy
(
items
.
begin
(),
items
.
end
(),
reinterpret_cast
<
T
*>
(
ptr
->
storage
()));
return
batch
{
std
::
move
(
ptr
)};
}
}
// namespace caf::async
#ifdef CAF_CLANG
# pragma clang diagnostic pop
#elif defined(CAF_GCC)
# pragma GCC diagnostic pop
#elif defined(CAF_MSVC)
# pragma warning(pop)
#endif
libcaf_core/caf/async/blocking_observer.hpp
0 → 100644
View file @
4c68deb8
// 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 <condition_variable>
#include <type_traits>
#include "caf/async/observer_buffer.hpp"
namespace
caf
::
async
{
/// Consumes all elements from a publisher and blocks the current thread until
/// completion.
template
<
class
T
>
class
blocking_observer
:
public
observer_buffer
<
T
>
{
public:
using
super
=
observer_buffer
<
T
>
;
using
super
::
super
;
template
<
class
OnNext
,
class
OnError
,
class
OnComplete
>
auto
run
(
OnNext
fun
,
OnError
err
,
OnComplete
fin
)
{
static_assert
(
std
::
is_invocable_v
<
OnNext
,
T
>
,
"OnNext handlers must have signature 'void(T)' or 'bool(T)'"
);
static_assert
(
std
::
is_invocable_v
<
OnError
,
error
>
,
"OnError handlers must have signature 'void(const error&)'"
);
static_assert
(
std
::
is_invocable_v
<
OnComplete
>
,
"OnComplete handlers must have signature 'void()'"
);
auto
wait_fn
=
[
this
](
std
::
unique_lock
<
std
::
mutex
>&
guard
)
{
cv_
.
wait
(
guard
);
};
for
(;;)
{
auto
[
val
,
done
,
what
]
=
super
::
wait_with
(
wait_fn
);
if
(
val
)
{
using
fun_res
=
decltype
(
fun
(
*
val
));
if
constexpr
(
std
::
is_same_v
<
bool
,
fun_res
>
)
{
if
(
!
fun
(
*
val
))
{
std
::
unique_lock
guard
{
super
::
mtx_
};
if
(
super
::
sub_
)
super
::
sub_
->
cancel
();
return
;
}
}
else
{
static_assert
(
std
::
is_same_v
<
void
,
fun_res
>
,
"OnNext handlers must have signature 'void(T)' or 'bool(T)'"
);
fun
(
*
val
);
}
}
else
if
(
done
)
{
if
(
!
what
)
fin
();
else
err
(
*
what
);
return
;
}
}
}
private:
std
::
condition_variable
cv_
;
void
wakeup
(
std
::
unique_lock
<
std
::
mutex
>&
)
override
{
cv_
.
notify_all
();
}
};
}
// namespace caf::async
libcaf_core/caf/async/bounded_buffer.hpp
0 → 100644
View file @
4c68deb8
// 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 <cstdlib>
#include <mutex>
#include "caf/async/consumer.hpp"
#include "caf/async/producer.hpp"
#include "caf/config.hpp"
#include "caf/defaults.hpp"
#include "caf/error.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/make_counted.hpp"
#include "caf/raise_error.hpp"
#include "caf/ref_counted.hpp"
#include "caf/sec.hpp"
#include "caf/span.hpp"
#include "caf/unit.hpp"
namespace
caf
::
async
{
/// Policy type for having `consume` call `on_error` immediately after the
/// producer has aborted even if the buffer still contains events.
struct
prioritize_errors_t
{
static
constexpr
bool
calls_on_error
=
true
;
};
/// @relates prioritize_errors_t
constexpr
auto
prioritize_errors
=
prioritize_errors_t
{};
/// Policy type for having `consume` call `on_error` only after processing all
/// events from the buffer.
struct
delay_errors_t
{
static
constexpr
bool
calls_on_error
=
true
;
};
/// @relates delay_errors_t
constexpr
auto
delay_errors
=
delay_errors_t
{};
/// Policy type for having `consume` treat errors as ordinary shutdowns.
struct
ignore_errors_t
{
static
constexpr
bool
calls_on_error
=
false
;
};
/// @relates ignore_errors_t
constexpr
auto
ignore_errors
=
ignore_errors_t
{};
/// A bounded buffer for transmitting events from one producer to one consumer.
template
<
class
T
>
class
bounded_buffer
:
public
ref_counted
{
public:
using
value_type
=
T
;
bounded_buffer
(
uint32_t
max_in_flight
,
uint32_t
min_pull_size
)
:
max_in_flight_
(
max_in_flight
),
min_pull_size_
(
min_pull_size
)
{
buf_
=
reinterpret_cast
<
T
*>
(
malloc
(
sizeof
(
T
)
*
max_in_flight
*
2
));
}
~
bounded_buffer
()
{
auto
first
=
buf_
+
rd_pos_
;
auto
last
=
buf_
+
wr_pos_
;
std
::
destroy
(
first
,
last
);
free
(
buf_
);
}
/// Appends to the buffer and calls `on_producer_wakeup` on the consumer if
/// the buffer becomes non-empty.
size_t
push
(
span
<
const
T
>
items
)
{
std
::
unique_lock
guard
{
mtx_
};
CAF_ASSERT
(
producer_
!=
nullptr
);
CAF_ASSERT
(
!
closed_
);
std
::
uninitialized_copy
(
items
.
begin
(),
items
.
end
(),
buf_
+
wr_pos_
);
wr_pos_
+=
items
.
size
();
if
(
size
()
==
items
.
size
()
&&
consumer_
)
consumer_
->
on_producer_wakeup
();
return
capacity
()
-
size
();
}
size_t
push
(
const
T
&
item
)
{
return
push
(
make_span
(
&
item
,
1
));
}
/// Consumes up to `demand` items from the buffer with `on_next`, ignoring any
/// errors set by the producer.
/// @tparam Policy Either `instant_error_t` or `delay_error_t`. Instant
/// error propagation requires also passing `on_error` handler.
/// Delaying errors without passing an `on_error` handler
/// effectively suppresses errors.
/// @returns `true` if no more elements are available, `false` otherwise.
template
<
class
Policy
,
class
OnNext
,
class
OnError
=
unit_t
>
bool
consume
(
Policy
,
size_t
demand
,
OnNext
on_next
,
OnError
on_error
=
OnError
{})
{
static
constexpr
size_t
local_buf_size
=
16
;
if
constexpr
(
Policy
::
calls_on_error
)
static_assert
(
!
std
::
is_same_v
<
OnError
,
unit_t
>
,
"Policy requires an on_error handler"
);
else
static_assert
(
std
::
is_same_v
<
OnError
,
unit_t
>
,
"Policy prohibits an on_error handler"
);
T
local_buf
[
local_buf_size
];
std
::
unique_lock
guard
{
mtx_
};
CAF_ASSERT
(
demand
>
0
);
CAF_ASSERT
(
consumer_
!=
nullptr
);
if
constexpr
(
std
::
is_same_v
<
Policy
,
prioritize_errors_t
>
)
{
if
(
err_
)
{
on_error
(
err_
);
consumer_
=
nullptr
;
return
true
;
}
}
auto
next_n
=
[
this
,
&
demand
]
{
return
std
::
min
({
local_buf_size
,
demand
,
size
()});
};
for
(
auto
n
=
next_n
();
n
>
0
;
n
=
next_n
())
{
auto
first
=
buf_
+
rd_pos_
;
std
::
move
(
first
,
first
+
n
,
local_buf
);
std
::
destroy
(
first
,
first
+
n
);
rd_pos_
+=
n
;
shift_elements
();
signal_demand
(
n
);
guard
.
unlock
();
on_next
(
make_span
(
local_buf
,
n
));
demand
-=
n
;
guard
.
lock
();
}
if
(
!
empty
()
||
!
closed_
)
{
return
false
;
}
else
{
if
constexpr
(
std
::
is_same_v
<
Policy
,
delay_errors_t
>
)
{
if
(
err_
)
on_error
(
err_
);
}
consumer_
=
nullptr
;
return
true
;
}
}
/// Checks whether there is any pending data in the buffer.
bool
has_data
()
const
noexcept
{
std
::
unique_lock
guard
{
mtx_
};
return
!
empty
();
}
/// Closes the buffer by request of the producer.
void
close
()
{
std
::
unique_lock
guard
{
mtx_
};
CAF_ASSERT
(
producer_
!=
nullptr
);
closed_
=
true
;
producer_
=
nullptr
;
if
(
empty
()
&&
consumer_
)
consumer_
->
on_producer_wakeup
();
}
/// Closes the buffer and signals an error by request of the producer.
void
abort
(
error
reason
)
{
std
::
unique_lock
guard
{
mtx_
};
closed_
=
true
;
err_
=
std
::
move
(
reason
);
producer_
=
nullptr
;
if
(
empty
()
&&
consumer_
)
{
consumer_
->
on_producer_wakeup
();
consumer_
=
nullptr
;
}
}
/// Closes the buffer by request of the consumer.
void
cancel
()
{
std
::
unique_lock
guard
{
mtx_
};
if
(
producer_
)
producer_
->
on_consumer_cancel
();
consumer_
=
nullptr
;
}
void
set_consumer
(
consumer_ptr
consumer
)
{
CAF_ASSERT
(
consumer
!=
nullptr
);
std
::
unique_lock
guard
{
mtx_
};
if
(
consumer_
)
CAF_RAISE_ERROR
(
"producer-consumer queue already has a consumer"
);
consumer_
=
std
::
move
(
consumer
);
if
(
producer_
)
ready
();
}
void
set_producer
(
producer_ptr
producer
)
{
CAF_ASSERT
(
producer
!=
nullptr
);
std
::
unique_lock
guard
{
mtx_
};
if
(
producer_
)
CAF_RAISE_ERROR
(
"producer-consumer queue already has a producer"
);
producer_
=
std
::
move
(
producer
);
if
(
consumer_
)
ready
();
}
size_t
capacity
()
const
noexcept
{
return
max_in_flight_
;
}
private:
void
ready
()
{
producer_
->
on_consumer_ready
();
consumer_
->
on_producer_ready
();
if
(
!
empty
())
consumer_
->
on_producer_wakeup
();
}
size_t
empty
()
const
noexcept
{
CAF_ASSERT
(
wr_pos_
>=
rd_pos_
);
return
wr_pos_
==
rd_pos_
;
}
size_t
size
()
const
noexcept
{
CAF_ASSERT
(
wr_pos_
>=
rd_pos_
);
return
wr_pos_
-
rd_pos_
;
}
void
shift_elements
()
{
if
(
rd_pos_
>=
max_in_flight_
)
{
if
(
empty
())
{
rd_pos_
=
0
;
wr_pos_
=
0
;
}
else
{
// No need to check for overlap: the first half of the buffer is
// empty.
auto
first
=
buf_
+
rd_pos_
;
auto
last
=
buf_
+
wr_pos_
;
std
::
uninitialized_move
(
first
,
last
,
buf_
);
std
::
destroy
(
first
,
last
);
wr_pos_
-=
rd_pos_
;
rd_pos_
=
0
;
}
}
}
void
signal_demand
(
uint32_t
new_demand
)
{
demand_
+=
new_demand
;
if
(
demand_
>=
min_pull_size_
&&
producer_
)
{
producer_
->
on_consumer_demand
(
demand_
);
demand_
=
0
;
}
}
/// Guards access to all other member variables.
mutable
std
::
mutex
mtx_
;
/// Allocated to max_in_flight_ * 2, but at most holds max_in_flight_
/// elements at any point in time. We dynamically shift elements into the
/// first half of the buffer whenever rd_pos_ crosses the midpoint.
T
*
buf_
;
/// Stores how many items the buffer may hold at any time.
uint32_t
max_in_flight_
;
/// Configures the minimum amount of free buffer slots that we signal to the
/// producer.
uint32_t
min_pull_size_
;
/// Stores the read position of the consumer.
uint32_t
rd_pos_
=
0
;
/// Stores the write position of the producer.
uint32_t
wr_pos_
=
0
;
/// Demand that has not yet been signaled back to the producer.
uint32_t
demand_
=
0
;
/// Stores whether `close` has been called.
bool
closed_
=
false
;
/// Stores the abort reason.
error
err_
;
/// Callback handle to the consumer.
consumer_ptr
consumer_
;
/// Callback handle to the producer.
producer_ptr
producer_
;
};
/// @relates bounded_buffer
template
<
class
T
>
using
bounded_buffer_ptr
=
intrusive_ptr
<
bounded_buffer
<
T
>>
;
/// @relates bounded_buffer
template
<
class
T
,
bool
IsProducer
>
struct
resource_ctrl
:
ref_counted
{
using
buffer_ptr
=
bounded_buffer_ptr
<
T
>
;
explicit
resource_ctrl
(
buffer_ptr
ptr
)
:
buf
(
std
::
move
(
ptr
))
{
// nop
}
~
resource_ctrl
()
{
if
(
buf
)
{
if
constexpr
(
IsProducer
)
{
auto
err
=
make_error
(
sec
::
invalid_upstream
,
"producer_resource destroyed without opening it"
);
buf
->
abort
(
err
);
}
else
{
buf
->
cancel
();
}
}
}
buffer_ptr
try_open
()
{
std
::
unique_lock
guard
{
mtx
};
if
(
buf
)
{
auto
res
=
buffer_ptr
{};
res
.
swap
(
buf
);
return
res
;
}
return
nullptr
;
}
mutable
std
::
mutex
mtx
;
buffer_ptr
buf
;
};
/// Grants read access to the first consumer that calls `open` on the resource.
/// Cancels consumption of items on the buffer if the resources gets destroyed
/// before opening it.
/// @relates bounded_buffer
template
<
class
T
>
class
consumer_resource
{
public:
using
value_type
=
T
;
using
buffer_type
=
bounded_buffer
<
T
>
;
using
buffer_ptr
=
bounded_buffer_ptr
<
T
>
;
explicit
consumer_resource
(
buffer_ptr
buf
)
{
ctrl_
.
emplace
(
std
::
move
(
buf
));
}
consumer_resource
()
=
default
;
consumer_resource
(
consumer_resource
&&
)
=
default
;
consumer_resource
(
const
consumer_resource
&
)
=
default
;
consumer_resource
&
operator
=
(
consumer_resource
&&
)
=
default
;
consumer_resource
&
operator
=
(
const
consumer_resource
&
)
=
default
;
/// Tries to open the resource for reading from the buffer. The first `open`
/// wins on concurrent access.
/// @returns a pointer to the buffer on success, `nullptr` otherwise.
buffer_ptr
try_open
()
{
if
(
ctrl_
)
{
auto
res
=
ctrl_
->
try_open
();
ctrl_
=
nullptr
;
return
res
;
}
else
{
return
nullptr
;
}
}
private:
intrusive_ptr
<
resource_ctrl
<
T
,
false
>>
ctrl_
;
};
/// Grants access to a buffer to the first producer that calls `open`. Aborts
/// writes on the buffer if the resources gets destroyed before opening it.
/// @relates bounded_buffer
template
<
class
T
>
class
producer_resource
{
public:
using
value_type
=
T
;
using
buffer_type
=
bounded_buffer
<
T
>
;
using
buffer_ptr
=
bounded_buffer_ptr
<
T
>
;
explicit
producer_resource
(
buffer_ptr
buf
)
{
ctrl_
.
emplace
(
std
::
move
(
buf
));
}
producer_resource
()
=
default
;
producer_resource
(
producer_resource
&&
)
=
default
;
producer_resource
(
const
producer_resource
&
)
=
default
;
producer_resource
&
operator
=
(
producer_resource
&&
)
=
default
;
producer_resource
&
operator
=
(
const
producer_resource
&
)
=
default
;
/// Tries to open the resource for writing to the buffer. The first `open`
/// wins on concurrent access.
/// @returns a pointer to the buffer on success, `nullptr` otherwise.
buffer_ptr
try_open
()
{
if
(
ctrl_
)
{
auto
res
=
ctrl_
->
try_open
();
ctrl_
=
nullptr
;
return
res
;
}
else
{
return
nullptr
;
}
}
private:
intrusive_ptr
<
resource_ctrl
<
T
,
true
>>
ctrl_
;
};
/// Creates bounded buffer and returns two resources connected by that buffer.
template
<
class
T
>
std
::
pair
<
consumer_resource
<
T
>
,
producer_resource
<
T
>>
make_bounded_buffer_resource
(
size_t
buffer_size
,
size_t
min_request_size
)
{
using
buffer_type
=
bounded_buffer
<
T
>
;
auto
buf
=
make_counted
<
buffer_type
>
(
buffer_size
,
min_request_size
);
return
{
async
::
consumer_resource
<
T
>
{
buf
},
async
::
producer_resource
<
T
>
{
buf
}};
}
/// Creates bounded buffer and returns two resources connected by that buffer.
template
<
class
T
>
std
::
pair
<
consumer_resource
<
T
>
,
producer_resource
<
T
>>
make_bounded_buffer_resource
()
{
return
make_bounded_buffer_resource
<
T
>
(
defaults
::
flow
::
buffer_size
,
defaults
::
flow
::
min_demand
);
}
}
// namespace caf::async
libcaf_core/caf/async/consumer.hpp
0 → 100644
View file @
4c68deb8
// 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/intrusive_ptr.hpp"
namespace
caf
::
async
{
/// Base type for asynchronous consumers of an event source.
class
CAF_CORE_EXPORT
consumer
{
public:
virtual
~
consumer
();
/// Called to signal that the producer started emitting items.
virtual
void
on_producer_ready
()
=
0
;
/// Called to signal to the consumer that the producer added an item to a
/// previously empty source or completed the flow of events.
virtual
void
on_producer_wakeup
()
=
0
;
/// Increases the reference count of the consumer.
virtual
void
ref_consumer
()
const
noexcept
=
0
;
/// Decreases the reference count of the consumer and destroys the object if
/// necessary.
virtual
void
deref_consumer
()
const
noexcept
=
0
;
};
/// @relates consumer
inline
void
intrusive_ptr_add_ref
(
const
consumer
*
p
)
noexcept
{
p
->
ref_consumer
();
}
/// @relates consumer
inline
void
intrusive_ptr_release
(
const
consumer
*
p
)
noexcept
{
p
->
deref_consumer
();
}
/// @relates consumer
using
consumer_ptr
=
intrusive_ptr
<
consumer
>
;
}
// namespace caf::async
libcaf_core/caf/async/fwd.hpp
0 → 100644
View file @
4c68deb8
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include <caf/fwd.hpp>
namespace
caf
::
async
{
// -- classes ------------------------------------------------------------------
class
batch
;
class
consumer
;
class
producer
;
// -- template classes ---------------------------------------------------------
template
<
class
T
>
class
bounded_buffer
;
template
<
class
T
>
class
consumer_resource
;
template
<
class
T
>
class
producer_resource
;
// -- free function templates --------------------------------------------------
template
<
class
T
>
batch
make_batch
(
span
<
const
T
>
items
);
}
// namespace caf::async
libcaf_core/caf/async/observer_buffer.hpp
0 → 100644
View file @
4c68deb8
// 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 <list>
#include <mutex>
#include <tuple>
#include "caf/async/batch.hpp"
#include "caf/defaults.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/subscription.hpp"
namespace
caf
::
async
{
/// Enables buffered consumption of published items.
template
<
class
T
>
class
observer_buffer
:
public
flow
::
observer_impl
<
T
>
{
public:
observer_buffer
()
{
// nop
}
void
on_complete
()
override
{
std
::
unique_lock
guard
{
mtx_
};
if
(
!
done_
)
{
sub_
=
nullptr
;
done_
=
true
;
deinit
(
guard
);
}
}
void
on_error
(
const
error
&
what
)
override
{
std
::
unique_lock
guard
{
mtx_
};
if
(
!
done_
)
{
sub_
=
nullptr
;
done_
=
true
;
err_
=
what
;
deinit
(
guard
);
}
}
void
on_next
(
span
<
const
T
>
items
)
override
{
on_batch
(
make_batch
(
items
));
}
void
on_batch
(
const
batch
&
buf
)
override
{
std
::
list
<
batch
>
ls
;
ls
.
emplace_back
(
buf
);
std
::
unique_lock
guard
{
mtx_
};
batches_
.
splice
(
batches_
.
end
(),
std
::
move
(
ls
));
if
(
batches_
.
size
()
==
1
)
wakeup
(
guard
);
}
void
on_attach
(
flow
::
subscription
sub
)
override
{
CAF_ASSERT
(
sub
.
valid
());
std
::
unique_lock
guard
{
mtx_
};
sub_
=
std
::
move
(
sub
);
init
(
guard
);
}
bool
has_data
()
{
if
(
local
.
pos_
!=
local
.
end_
)
{
return
true
;
}
else
{
std
::
unique_lock
guard
{
mtx_
};
return
!
batches_
.
empty
();
}
}
/// Tries to fetch the next value. If no value exists, the first element in
/// the tuple is `nullptr`. The second value indicates whether the stream was
/// closed. If the stream was closed, the third value is `nullptr` if
/// `on_complete` was called and a pointer to the error if `on_error` was
/// called.
std
::
tuple
<
const
T
*
,
bool
,
const
error
*>
poll
()
{
if
(
local
.
pos_
!=
local
.
end_
)
{
auto
res
=
local
.
pos_
;
if
(
++
local
.
pos_
==
local
.
end_
)
{
std
::
unique_lock
guard
{
mtx_
};
if
(
sub_
)
sub_
.
request
(
local
.
cache_
.
size
());
}
return
{
res
,
false
,
nullptr
};
}
else
if
(
std
::
unique_lock
guard
{
mtx_
};
!
batches_
.
empty
())
{
batches_
.
front
().
swap
(
local
.
cache_
);
batches_
.
pop_front
();
guard
.
unlock
();
auto
items
=
local
.
cache_
.
template
items
<
T
>();
CAF_ASSERT
(
!
items
.
empty
());
local
.
pos_
=
items
.
begin
();
local
.
end_
=
items
.
end
();
return
{
local
.
pos_
++
,
false
,
nullptr
};
}
else
if
(
!
err_
)
{
return
{
nullptr
,
done_
,
nullptr
};
}
else
{
return
{
nullptr
,
true
,
&
err_
};
}
}
void
dispose
()
override
{
on_complete
();
}
bool
disposed
()
const
noexcept
override
{
std
::
unique_lock
guard
{
mtx_
};
return
done_
;
}
protected:
template
<
class
WaitFn
>
std
::
tuple
<
const
T
*
,
bool
,
const
error
*>
wait_with
(
WaitFn
wait_fn
)
{
if
(
local
.
pos_
!=
local
.
end_
)
{
auto
res
=
local
.
pos_
;
if
(
++
local
.
pos_
==
local
.
end_
)
{
std
::
unique_lock
guard
{
mtx_
};
if
(
sub_
)
sub_
.
request
(
local
.
cache_
.
size
());
}
return
{
res
,
false
,
nullptr
};
}
else
{
std
::
unique_lock
guard
{
mtx_
};
while
(
batches_
.
empty
()
&&
!
done_
)
wait_fn
(
guard
);
if
(
!
batches_
.
empty
())
{
batches_
.
front
().
swap
(
local
.
cache_
);
batches_
.
pop_front
();
guard
.
unlock
();
auto
items
=
local
.
cache_
.
template
items
<
T
>();
local
.
pos_
=
items
.
begin
();
local
.
end_
=
items
.
end
();
return
{
local
.
pos_
++
,
false
,
nullptr
};
}
else
if
(
!
err_
)
{
return
{
nullptr
,
done_
,
nullptr
};
}
else
{
return
{
nullptr
,
true
,
&
err_
};
}
}
}
/// Wraps fields that we only access from the consumer's thread.
struct
local_t
{
const
T
*
pos_
=
nullptr
;
const
T
*
end_
=
nullptr
;
batch
cache_
;
}
local
;
static_assert
(
sizeof
(
local_t
)
<
CAF_CACHE_LINE_SIZE
);
/// Avoids false sharing.
char
pad_
[
CAF_CACHE_LINE_SIZE
-
sizeof
(
local_t
)];
// -- state for consumer and publisher ---------------------------------------
/// Protects fields that we access with both the consumer and the producer.
mutable
std
::
mutex
mtx_
;
flow
::
subscription
sub_
;
bool
done_
=
false
;
error
err_
;
std
::
list
<
batch
>
batches_
;
private:
virtual
void
init
(
std
::
unique_lock
<
std
::
mutex
>&
)
{
sub_
.
request
(
defaults
::
flow
::
buffer_size
);
}
virtual
void
deinit
(
std
::
unique_lock
<
std
::
mutex
>&
guard
)
{
wakeup
(
guard
);
}
virtual
void
wakeup
(
std
::
unique_lock
<
std
::
mutex
>&
)
{
// Customization point.
}
};
}
// namespace caf::async
libcaf_core/caf/async/producer.hpp
0 → 100644
View file @
4c68deb8
// 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/intrusive_ptr.hpp"
namespace
caf
::
async
{
/// Base type for asynchronous producers of events.
class
CAF_CORE_EXPORT
producer
{
public:
virtual
~
producer
();
/// Called to signal that the consumer started handling events.
virtual
void
on_consumer_ready
()
=
0
;
/// Called to signal that the consumer stopped handling events.
virtual
void
on_consumer_cancel
()
=
0
;
/// Called to signal that the consumer requests more events.
virtual
void
on_consumer_demand
(
size_t
demand
)
=
0
;
/// Increases the reference count of the producer.
virtual
void
ref_producer
()
const
noexcept
=
0
;
/// Decreases the reference count of the producer and destroys the object if
/// necessary.
virtual
void
deref_producer
()
const
noexcept
=
0
;
};
/// @relates producer
inline
void
intrusive_ptr_add_ref
(
const
producer
*
p
)
noexcept
{
p
->
ref_producer
();
}
/// @relates producer
inline
void
intrusive_ptr_release
(
const
producer
*
p
)
noexcept
{
p
->
deref_producer
();
}
/// @relates producer
using
producer_ptr
=
intrusive_ptr
<
producer
>
;
}
// namespace caf::async
libcaf_core/caf/callback.hpp
View file @
4c68deb8
...
...
@@ -4,6 +4,7 @@
#pragma once
#include "caf/allowed_unsafe_message_type.hpp"
#include "caf/config.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/error.hpp"
...
...
@@ -91,4 +92,10 @@ auto make_shared_type_erased_callback(F fun) {
return
shared_callback_ptr
<
signature
>
{
std
::
move
(
res
)};
}
/// Convenience type alias for an action with shared ownership.
/// @relates callback
using
shared_action_ptr
=
shared_callback_ptr
<
void
()
>
;
}
// namespace caf
CAF_ALLOW_UNSAFE_MESSAGE_TYPE
(
caf
::
shared_action_ptr
)
libcaf_core/caf/defaults.hpp
View file @
4c68deb8
...
...
@@ -115,3 +115,11 @@ constexpr auto cached_udp_buffers = size_t{10};
constexpr
auto
max_pending_msgs
=
size_t
{
10
};
}
// namespace caf::defaults::middleman
namespace
caf
::
defaults
::
flow
{
constexpr
auto
min_demand
=
size_t
{
8
};
constexpr
auto
batch_size
=
size_t
{
32
};
constexpr
auto
buffer_size
=
size_t
{
128
};
}
// namespace caf::defaults::flow
libcaf_core/caf/disposable.hpp
View file @
4c68deb8
...
...
@@ -15,9 +15,13 @@ namespace caf {
/// Represents a disposable resource.
class
CAF_CORE_EXPORT
disposable
{
public:
// -- member types -----------------------------------------------------------
/// Internal implementation class of a `disposable`.
class
impl
{
public:
CAF_INTRUSIVE_PTR_FRIENDS_SFX
(
impl
,
_disposable
)
virtual
~
impl
();
virtual
void
dispose
()
=
0
;
...
...
@@ -29,14 +33,6 @@ public:
virtual
void
ref_disposable
()
const
noexcept
=
0
;
virtual
void
deref_disposable
()
const
noexcept
=
0
;
friend
void
intrusive_ptr_add_ref
(
const
impl
*
ptr
)
noexcept
{
ptr
->
ref_disposable
();
}
friend
void
intrusive_ptr_release
(
const
impl
*
ptr
)
noexcept
{
ptr
->
deref_disposable
();
}
};
explicit
disposable
(
intrusive_ptr
<
impl
>
pimpl
)
noexcept
...
...
@@ -44,17 +40,27 @@ public:
// nop
}
// -- constructors, destructors, and assignment operators --------------------
disposable
()
noexcept
=
default
;
disposable
(
disposable
&&
)
noexcept
=
default
;
disposable
(
const
disposable
&
)
noexcept
=
default
;
disposable
&
operator
=
(
disposable
&&
)
noexcept
=
default
;
disposable
&
operator
=
(
const
disposable
&
)
noexcept
=
default
;
// -- factories --------------------------------------------------------------
/// Combines multiple disposables into a single disposable. The new disposable
/// is disposed if all of its elements are disposed. Disposing the composite
/// disposes all elements individually.
static
disposable
make_composite
(
std
::
vector
<
disposable
>
entries
);
// -- mutators ---------------------------------------------------------------
/// Disposes the resource. Calling `dispose()` on a disposed resource is a
/// no-op.
void
dispose
()
{
...
...
@@ -64,6 +70,13 @@ public:
}
}
/// Exchanges the content of this handle with `other`.
void
swap
(
disposable
&
other
)
{
pimpl_
.
swap
(
other
.
pimpl_
);
}
// -- properties -------------------------------------------------------------
/// Returns whether the resource has been disposed.
[[
nodiscard
]]
bool
disposed
()
const
noexcept
{
return
pimpl_
?
pimpl_
->
disposed
()
:
true
;
...
...
@@ -89,6 +102,8 @@ public:
return
pimpl_
.
get
();
}
// -- conversions ------------------------------------------------------------
/// Returns a smart pointer to the implementation.
[[
nodiscard
]]
intrusive_ptr
<
impl
>&&
as_intrusive_ptr
()
&&
noexcept
{
return
std
::
move
(
pimpl_
);
...
...
@@ -99,11 +114,6 @@ public:
return
pimpl_
;
}
/// Exchanges the content of this handle with `other`.
void
swap
(
disposable
&
other
)
{
pimpl_
.
swap
(
other
.
pimpl_
);
}
private:
intrusive_ptr
<
impl
>
pimpl_
;
};
...
...
libcaf_core/caf/event_based_actor.hpp
View file @
4c68deb8
...
...
@@ -48,6 +48,8 @@ public:
/// Required by `spawn` for type deduction.
using
behavior_type
=
behavior
;
using
handle_type
=
actor
;
// -- constructors, destructors ----------------------------------------------
explicit
event_based_actor
(
actor_config
&
cfg
);
...
...
libcaf_core/caf/flow/concat.hpp
0 → 100644
View file @
4c68deb8
// 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"
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
<
merger_impl
<
output_type
>>
(
hdl
.
ptr
()
->
ctx
());
ptr
->
concat_mode
(
true
);
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/coordinator.hpp
0 → 100644
View file @
4c68deb8
// 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 <tuple>
#include "caf/action.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/flow/fwd.hpp"
#include "caf/flow/subscription.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/ref_counted.hpp"
namespace
caf
::
flow
{
/// Coordinates any number of co-located publishers and observers. The
/// co-located objects never need to synchronize calls to other co-located
/// objects since the coordinator guarantees synchronous execution.
class
CAF_CORE_EXPORT
coordinator
{
public:
// class CAF_CORE_EXPORT subscription_impl : public subscription::impl {
// public:
// friend class coordinator;
// subscription_impl(coordinator* ctx, observable_base_ptr src,
// observer_base_ptr snk)
// : ctx_(ctx), src_(std::move(src)), snk_(std::move(snk)) {
// // nop
// }
// void request(size_t n) final;
// void cancel() final;
// bool disposed() const noexcept final;
// auto* ctx() const noexcept {
// return ctx_;
// }
// private:
// coordinator* ctx_;
// observable_base_ptr src_;
// observer_base_ptr snk_;
// };
// using subscription_impl_ptr = intrusive_ptr<subscription_impl>;
friend
class
subscription_impl
;
virtual
~
coordinator
();
[[
nodiscard
]]
observable_builder
make_observable
();
/// Increases the reference count of the coordinator.
virtual
void
ref_coordinator
()
const
noexcept
=
0
;
/// Decreases the reference count of the coordinator and destroys the object
/// if necessary.
virtual
void
deref_coordinator
()
const
noexcept
=
0
;
/// Schedules an action for execution on this coordinator. This member
/// function may get called from external sources or threads.
/// @thread-safe
virtual
void
schedule
(
action
what
)
=
0
;
///@copydoc schedule
template
<
class
F
>
void
schedule_fn
(
F
&&
what
)
{
return
schedule
(
make_action
(
std
::
forward
<
F
>
(
what
)));
}
/// Schedules an action for execution from within the coordinator. May call
/// `schedule` for coordinators that use a single work queue.
virtual
void
post_internally
(
action
what
)
=
0
;
///@copydoc schedule
template
<
class
F
>
void
post_internally_fn
(
F
&&
what
)
{
return
post_internally
(
make_action
(
std
::
forward
<
F
>
(
what
)));
}
/// Asks the coordinator to keep its event loop running until `obj` becomes
/// disposed since it depends on external events or produces events that are
/// visible to outside observers.
virtual
void
watch
(
disposable
what
)
=
0
;
friend
void
intrusive_ptr_add_ref
(
const
coordinator
*
ptr
)
noexcept
{
ptr
->
ref_coordinator
();
}
friend
void
intrusive_ptr_release
(
const
coordinator
*
ptr
)
noexcept
{
ptr
->
deref_coordinator
();
}
};
/// @relates coordinator
using
coordinator_ptr
=
intrusive_ptr
<
coordinator
>
;
}
// namespace caf::flow
libcaf_core/caf/flow/fwd.hpp
0 → 100644
View file @
4c68deb8
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/fwd.hpp"
#include <type_traits>
namespace
caf
::
detail
{
template
<
class
Left
,
class
Right
>
struct
left_oracle
{
using
type
=
Left
;
};
/// Useful to force dependent evaluation of Left. For example in template
/// functions where only Right is actually a template parameter.
template
<
class
Left
,
class
Right
>
using
left_t
=
typename
left_oracle
<
Left
,
Right
>::
type
;
}
// namespace caf::detail
namespace
caf
::
flow
{
class
coordinator
;
class
subscription
;
template
<
class
T
>
class
single
;
template
<
class
T
>
class
observer
;
template
<
class
T
>
class
observable
;
template
<
class
In
,
class
Out
>
class
processor
;
template
<
class
Generator
,
class
...
Steps
>
class
generation
;
template
<
class
Step
,
class
...
Steps
>
class
transformation
;
template
<
class
T
>
struct
is_observable
{
static
constexpr
bool
value
=
false
;
};
template
<
class
T
>
struct
is_observable
<
observable
<
T
>>
{
static
constexpr
bool
value
=
true
;
};
template
<
class
Step
,
class
...
Steps
>
struct
is_observable
<
transformation
<
Step
,
Steps
...
>>
{
static
constexpr
bool
value
=
true
;
};
template
<
class
Generator
,
class
...
Steps
>
struct
is_observable
<
generation
<
Generator
,
Steps
...
>>
{
static
constexpr
bool
value
=
true
;
};
template
<
class
In
,
class
Out
>
struct
is_observable
<
processor
<
In
,
Out
>>
{
static
constexpr
bool
value
=
true
;
};
template
<
class
T
>
struct
is_observable
<
single
<
T
>>
{
static
constexpr
bool
value
=
true
;
};
template
<
class
T
>
constexpr
bool
is_observable_v
=
is_observable
<
T
>::
value
;
template
<
class
T
>
struct
is_observer
{
static
constexpr
bool
value
=
false
;
};
template
<
class
T
>
struct
is_observer
<
observer
<
T
>>
{
static
constexpr
bool
value
=
true
;
};
template
<
class
Step
,
class
...
Steps
>
struct
is_observer
<
transformation
<
Step
,
Steps
...
>>
{
static
constexpr
bool
value
=
true
;
};
template
<
class
In
,
class
Out
>
struct
is_observer
<
processor
<
In
,
Out
>>
{
static
constexpr
bool
value
=
true
;
};
template
<
class
T
>
constexpr
bool
is_observer_v
=
is_observer
<
T
>::
value
;
class
observable_builder
;
template
<
class
T
>
struct
input_type_oracle
{
using
type
=
typename
T
::
input_type
;
};
template
<
class
T
>
using
input_type_t
=
typename
input_type_oracle
<
T
>::
type
;
template
<
class
T
>
struct
output_type_oracle
{
using
type
=
typename
T
::
output_type
;
};
template
<
class
T
>
using
output_type_t
=
typename
output_type_oracle
<
T
>::
type
;
template
<
class
T
>
class
merger_impl
;
template
<
class
>
struct
has_impl_include
{
static
constexpr
bool
value
=
false
;
};
template
<
class
T
>
constexpr
bool
has_impl_include_v
=
has_impl_include
<
T
>::
value
;
template
<
class
T
>
struct
assert_scheduled_actor_hdr
{
static
constexpr
bool
value
=
has_impl_include_v
<
detail
::
left_t
<
scheduled_actor
,
T
>>
;
static_assert
(
value
,
"include 'caf/scheduled_actor/flow.hpp' for this method"
);
};
template
<
class
T
>
using
assert_scheduled_actor_hdr_t
=
std
::
enable_if_t
<
assert_scheduled_actor_hdr
<
T
>::
value
,
T
>
;
}
// namespace caf::flow
libcaf_core/caf/flow/merge.hpp
0 → 100644
View file @
4c68deb8
// 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"
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
>
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
libcaf_core/caf/flow/observable.hpp
0 → 100644
View file @
4c68deb8
// 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 <cstddef>
#include <numeric>
#include <type_traits>
#include <vector>
#include "caf/async/bounded_buffer.hpp"
#include "caf/async/consumer.hpp"
#include "caf/async/producer.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/disposable.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/fwd.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/step.hpp"
#include "caf/flow/subscription.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/logger.hpp"
#include "caf/make_counted.hpp"
#include "caf/ref_counted.hpp"
#include "caf/sec.hpp"
#include "caf/span.hpp"
namespace
caf
::
flow
{
/// Represents a potentially unbound sequence of values.
template
<
class
T
>
class
observable
{
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
;
// -- flow processing ------------------------------------------------------
/// Subscribes a new observer.
virtual
disposable
subscribe
(
observer
<
T
>
what
)
=
0
;
virtual
void
on_request
(
observer_impl
<
T
>*
sink
,
size_t
n
)
=
0
;
virtual
void
on_cancel
(
observer_impl
<
T
>*
sink
)
=
0
;
protected:
disposable
do_subscribe
(
observer_impl
<
T
>*
snk
);
};
class
sub_impl
final
:
public
ref_counted
,
public
subscription
::
impl
{
public:
using
src_type
=
typename
observable
<
T
>::
impl
;
using
snk_type
=
typename
observer
<
T
>::
impl
;
CAF_INTRUSIVE_PTR_FRIENDS
(
sub_impl
)
sub_impl
(
coordinator
*
ctx
,
src_type
*
src
,
snk_type
*
snk
)
:
ctx_
(
ctx
),
src_
(
src
),
snk_
(
snk
)
{
// nop
}
bool
disposed
()
const
noexcept
override
{
return
src_
==
nullptr
;
}
void
ref_disposable
()
const
noexcept
override
{
this
->
ref
();
}
void
deref_disposable
()
const
noexcept
override
{
this
->
deref
();
}
void
request
(
size_t
n
)
override
{
if
(
src_
)
ctx
()
->
post_internally_fn
([
src
=
src_
,
snk
=
snk_
,
n
]
{
//
src
->
on_request
(
snk
.
get
(),
n
);
});
}
void
cancel
()
override
{
if
(
src_
)
{
ctx
()
->
post_internally_fn
([
src
=
src_
,
snk
=
snk_
]
{
//
src
->
on_cancel
(
snk
.
get
());
});
src_
.
reset
();
snk_
.
reset
();
}
}
auto
*
ctx
()
const
noexcept
{
return
ctx_
;
}
private:
coordinator
*
ctx_
;
intrusive_ptr
<
src_type
>
src_
;
intrusive_ptr
<
snk_type
>
snk_
;
};
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
;
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
);
/// 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 by concatenating the outputs of
/// all observables returned by `f`.
template
<
class
F
>
auto
concat_map
(
F
f
);
/// Creates an asynchronous resource that makes emitted items available in a
/// bounded 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_
);
}
private:
intrusive_ptr
<
impl
>
pimpl_
;
};
template
<
class
T
>
disposable
observable
<
T
>::
impl
::
do_subscribe
(
observer_impl
<
T
>*
snk
)
{
auto
ptr
=
make_counted
<
sub_impl
>
(
ctx
(),
this
,
snk
);
snk
->
on_subscribe
(
subscription
{
ptr
});
return
disposable
{
std
::
move
(
ptr
)};
}
template
<
class
T
>
using
observable_impl
=
typename
observable
<
T
>::
impl
;
/// Base type for classes that represent a definition of an `observable` which
/// has not yet been converted to an actual `observable`.
template
<
class
T
>
class
observable_def
{
public:
virtual
~
observable_def
()
=
default
;
template
<
class
OnNext
>
auto
for_each
(
OnNext
on_next
)
&&
{
return
lift
().
for_each
(
std
::
move
(
on_next
));
}
template
<
class
OnNext
,
class
OnError
>
auto
for_each
(
OnNext
on_next
,
OnError
on_error
)
&&
{
return
lift
().
for_each
(
std
::
move
(
on_next
),
std
::
move
(
on_error
));
}
template
<
class
OnNext
,
class
OnError
,
class
OnComplete
>
auto
for_each
(
OnNext
on_next
,
OnError
on_error
,
OnComplete
on_complete
)
&&
{
return
lift
().
for_each
(
std
::
move
(
on_next
),
std
::
move
(
on_error
),
std
::
move
(
on_complete
));
}
template
<
class
F
>
auto
flat_map
(
F
f
)
&&
{
return
lift
().
flat_map
(
std
::
move
(
f
));
}
template
<
class
F
>
auto
concat_map
(
F
f
)
&&
{
return
lift
().
concat_map
(
std
::
move
(
f
));
}
disposable
subscribe
(
observer
<
T
>
what
)
&&
{
return
lift
().
subscribe
(
std
::
move
(
what
));
}
disposable
subscribe
(
async
::
producer_resource
<
T
>
resource
)
&&
{
return
lift
().
subscribe
(
std
::
move
(
resource
));
}
async
::
consumer_resource
<
T
>
to_resource
()
&&
{
return
lift
().
to_resource
();
}
async
::
consumer_resource
<
T
>
to_resource
(
size_t
buffer_size
,
size_t
min_request_size
)
&&
{
return
lift
().
to_resource
(
buffer_size
,
min_request_size
);
}
observable
<
T
>
observe_on
(
coordinator
*
other
)
&&
{
return
lift
().
observe_on
(
other
);
}
observable
<
T
>
observe_on
(
coordinator
*
other
,
size_t
buffer_size
,
size_t
min_request_size
)
&&
{
return
lift
().
observe_on
(
other
,
buffer_size
,
min_request_size
);
}
virtual
observable
<
T
>
as_observable
()
&&
=
0
;
private:
decltype
(
auto
)
lift
()
{
return
std
::
move
(
*
this
).
as_observable
();
}
};
template
<
class
In
,
class
Out
>
class
processor
{
public:
class
impl
:
public
observer_impl
<
In
>
,
public
observable_impl
<
Out
>
{
public:
observer_impl
<
In
>*
as_observer_ptr
()
noexcept
{
return
this
;
}
observer
<
In
>
as_observer
()
noexcept
{
return
observer
<
In
>
{
as_observer_ptr
()};
}
observable_impl
<
In
>*
as_observable_ptr
()
noexcept
{
return
this
;
}
observable
<
In
>
as_observable
()
noexcept
{
return
observable
<
In
>
{
as_observable_ptr
()};
}
};
explicit
processor
(
intrusive_ptr
<
impl
>
pimpl
)
noexcept
:
pimpl_
(
std
::
move
(
pimpl
))
{
// nop
}
processor
&
operator
=
(
std
::
nullptr_t
)
noexcept
{
pimpl_
.
reset
();
return
*
this
;
}
using
input_type
=
In
;
using
output_type
=
Out
;
processor
()
noexcept
=
default
;
processor
(
processor
&&
)
noexcept
=
default
;
processor
(
const
processor
&
)
noexcept
=
default
;
processor
&
operator
=
(
processor
&&
)
noexcept
=
default
;
processor
&
operator
=
(
const
processor
&
)
noexcept
=
default
;
// -- conversion -------------------------------------------------------------
disposable
as_disposable
()
const
&
noexcept
{
return
as_observable
().
as_disposable
();
}
disposable
as_disposable
()
&&
noexcept
{
return
std
::
move
(
*
this
).
as_observable
().
as_disposable
();
}
observer
<
In
>
as_observer
()
const
&
noexcept
{
return
pimpl_
->
as_observer
();
}
observer
<
In
>
as_observer
()
&&
noexcept
{
auto
raw
=
pimpl_
.
release
()
->
as_observer_ptr
();
auto
ptr
=
intrusive_ptr
<
observer_impl
<
In
>>
{
raw
,
false
};
return
observer
<
In
>
{
std
::
move
(
ptr
)};
}
observable
<
Out
>
as_observable
()
const
&
noexcept
{
return
pimpl_
->
as_observable
();
}
observable
<
Out
>
as_observable
()
&&
noexcept
{
auto
raw
=
pimpl_
.
release
()
->
as_observable_ptr
();
auto
ptr
=
intrusive_ptr
<
observable_impl
<
Out
>>
{
raw
,
false
};
return
observable
<
Out
>
{
std
::
move
(
ptr
)};
}
private:
intrusive_ptr
<
impl
>
pimpl_
;
};
template
<
class
In
,
class
Out
>
using
processor_impl
=
typename
processor
<
In
,
Out
>::
impl
;
// -- representing an error as an observable -----------------------------------
template
<
class
T
>
class
observable_error_impl
:
public
ref_counted
,
public
observable_impl
<
T
>
{
public:
// -- member types -----------------------------------------------------------
using
output_type
=
T
;
// -- friends ----------------------------------------------------------------
CAF_INTRUSIVE_PTR_FRIENDS
(
observable_error_impl
)
// -- constructors, destructors, and assignment operators --------------------
observable_error_impl
(
coordinator
*
ctx
,
error
what
)
:
ctx_
(
ctx
),
what_
(
std
::
move
(
what
))
{
// nop
}
// -- implementation of disposable::impl -------------------------------------
void
dispose
()
override
{
// nop
}
bool
disposed
()
const
noexcept
override
{
return
true
;
}
void
ref_disposable
()
const
noexcept
override
{
this
->
ref
();
}
void
deref_disposable
()
const
noexcept
override
{
this
->
deref
();
}
// -- implementation of observable<T>::impl ----------------------------------
coordinator
*
ctx
()
const
noexcept
override
{
return
ctx_
;
}
void
on_request
(
observer_impl
<
T
>*
,
size_t
)
override
{
CAF_RAISE_ERROR
(
"observable_error_impl::on_request called"
);
}
void
on_cancel
(
observer_impl
<
T
>*
)
override
{
CAF_RAISE_ERROR
(
"observable_error_impl::on_cancel called"
);
}
disposable
subscribe
(
observer
<
T
>
what
)
override
{
what
.
on_error
(
what_
);
return
{};
}
private:
coordinator
*
ctx_
;
error
what_
;
};
// -- broadcasting -------------------------------------------------------------
/// Base type for processors with a buffer that broadcasts output to all
/// observers.
template
<
class
T
>
class
buffered_observable_impl
:
public
ref_counted
,
public
observable_impl
<
T
>
{
public:
// -- member types -----------------------------------------------------------
using
super
=
observable_impl
<
T
>
;
using
handle_type
=
observable
<
T
>
;
struct
output_t
{
size_t
demand
;
observer
<
T
>
sink
;
};
// -- friends ----------------------------------------------------------------
CAF_INTRUSIVE_PTR_FRIENDS
(
buffered_observable_impl
)
// -- constructors, destructors, and assignment operators --------------------
explicit
buffered_observable_impl
(
coordinator
*
ctx
)
:
ctx_
(
ctx
),
desired_capacity_
(
defaults
::
flow
::
buffer_size
)
{
buf_
.
reserve
(
desired_capacity_
);
}
buffered_observable_impl
(
coordinator
*
ctx
,
size_t
desired_capacity
)
:
ctx_
(
ctx
),
desired_capacity_
(
desired_capacity
)
{
buf_
.
reserve
(
desired_capacity_
);
}
// -- implementation of disposable::impl -------------------------------------
void
dispose
()
override
{
if
(
!
completed_
)
{
completed_
=
true
;
buf_
.
clear
();
for
(
auto
&
out
:
outputs_
)
out
.
sink
.
on_complete
();
outputs_
.
clear
();
do_on_complete
();
}
}
bool
disposed
()
const
noexcept
override
{
return
done
()
&&
outputs_
.
empty
();
}
void
ref_disposable
()
const
noexcept
override
{
this
->
ref
();
}
void
deref_disposable
()
const
noexcept
override
{
this
->
deref
();
}
// -- implementation of observable<T>::impl ----------------------------------
coordinator
*
ctx
()
const
noexcept
override
{
return
ctx_
;
}
void
on_request
(
observer_impl
<
T
>*
sink
,
size_t
n
)
override
{
if
(
auto
i
=
find
(
sink
);
i
!=
outputs_
.
end
())
{
i
->
demand
+=
n
;
update_max_demand
();
try_push
();
}
}
void
on_cancel
(
observer_impl
<
T
>*
sink
)
override
{
if
(
auto
i
=
find
(
sink
);
i
!=
outputs_
.
end
())
{
outputs_
.
erase
(
i
);
if
(
outputs_
.
empty
())
{
shutdown
();
}
else
{
update_max_demand
();
try_push
();
}
}
}
disposable
subscribe
(
observer
<
T
>
sink
)
override
{
if
(
done
())
{
sink
.
on_complete
();
return
disposable
{};
}
else
{
max_demand_
=
0
;
outputs_
.
emplace_back
(
output_t
{
0u
,
sink
});
return
super
::
do_subscribe
(
sink
.
ptr
());
}
}
// -- properties -------------------------------------------------------------
size_t
has_observers
()
const
noexcept
{
return
!
outputs_
.
empty
();
}
virtual
bool
done
()
const
noexcept
{
return
completed_
&&
buf_
.
empty
();
}
// -- buffer handling --------------------------------------------------------
template
<
class
Iterator
,
class
Sentinel
>
void
append_to_buf
(
Iterator
first
,
Sentinel
last
)
{
buf_
.
insert
(
buf_
.
end
(),
first
,
last
);
}
template
<
class
Val
>
void
append_to_buf
(
Val
&&
val
)
{
buf_
.
emplace_back
(
std
::
forward
<
Val
>
(
val
));
}
/// Stops the source, but allows observers to still consume buffered data.
virtual
void
shutdown
()
{
if
(
!
completed_
)
{
completed_
=
true
;
if
(
done
())
{
for
(
auto
&
out
:
outputs_
)
out
.
sink
.
on_complete
();
outputs_
.
clear
();
do_on_complete
();
}
}
}
/// Stops the source and drops any remaining data.
virtual
void
abort
(
const
error
&
reason
)
{
if
(
!
completed_
)
{
completed_
=
true
;
for
(
auto
&
out
:
outputs_
)
out
.
sink
.
on_error
(
reason
);
outputs_
.
clear
();
do_on_error
(
reason
);
}
}
/// Tries to push data from the buffer downstream.
void
try_push
()
{
if
(
!
batch_
.
empty
())
{
// Can only be true if a sink calls try_push in on_next.
return
;
}
size_t
batch_size
=
std
::
min
(
desired_capacity_
,
defaults
::
flow
::
batch_size
);
while
(
max_demand_
>
0
)
{
// Try to ship full batches.
if
(
batch_size
>
buf_
.
size
())
pull
(
batch_size
-
buf_
.
size
());
auto
n
=
std
::
min
(
max_demand_
,
buf_
.
size
());
if
(
n
==
0
)
break
;
batch_
.
assign
(
std
::
make_move_iterator
(
buf_
.
begin
()),
std
::
make_move_iterator
(
buf_
.
begin
()
+
n
));
buf_
.
erase
(
buf_
.
begin
(),
buf_
.
begin
()
+
n
);
auto
items
=
span
<
const
T
>
{
batch_
};
for
(
auto
&
out
:
outputs_
)
{
out
.
demand
-=
n
;
out
.
sink
.
on_next
(
items
);
}
max_demand_
-=
n
;
batch_
.
clear
();
if
(
done
())
{
for
(
auto
&
out
:
outputs_
)
out
.
sink
.
on_complete
();
outputs_
.
clear
();
break
;
}
}
}
auto
find
(
observer_impl
<
T
>*
sink
)
{
auto
pred
=
[
sink
](
auto
&
out
)
{
return
out
.
sink
.
ptr
()
==
sink
;
};
return
std
::
find_if
(
outputs_
.
begin
(),
outputs_
.
end
(),
pred
);
}
protected:
void
update_max_demand
()
{
if
(
outputs_
.
empty
())
{
max_demand_
=
0
;
}
else
{
auto
i
=
outputs_
.
begin
();
auto
e
=
outputs_
.
end
();
auto
init
=
(
*
i
++
).
demand
;
auto
f
=
[](
size_t
x
,
auto
&
out
)
{
return
std
::
min
(
x
,
out
.
demand
);
};
max_demand_
=
std
::
accumulate
(
i
,
e
,
init
,
f
);
}
}
coordinator
*
ctx_
;
size_t
desired_capacity_
;
std
::
vector
<
T
>
buf_
;
bool
completed_
=
false
;
size_t
max_demand_
=
0
;
std
::
vector
<
output_t
>
outputs_
;
/// Stores items right before calling on_next on the sinks.
std
::
vector
<
T
>
batch_
;
private:
/// Customization point for generating more data.
virtual
void
pull
(
size_t
)
{
// nop
}
/// Customization point for adding cleanup logic.
virtual
void
do_on_complete
()
{
// nop
}
/// Customization point for adding error handling logic.
virtual
void
do_on_error
(
const
error
&
)
{
// nop
}
};
template
<
class
T
>
using
buffered_observable_impl_ptr
=
intrusive_ptr
<
buffered_observable_impl
<
T
>>
;
template
<
class
T
>
struct
term_step
{
buffered_observable_impl
<
T
>*
pimpl
;
using
output_type
=
T
;
bool
on_next
(
const
T
&
item
)
{
pimpl
->
append_to_buf
(
item
);
return
true
;
}
void
on_complete
()
{
pimpl
->
shutdown
();
}
void
on_error
(
const
error
&
what
)
{
pimpl
->
abort
(
what
);
}
};
/// Base type for processors with a buffer that broadcasts output to all
/// observers.
template
<
class
In
,
class
Out
>
class
buffered_processor_impl
:
public
buffered_observable_impl
<
Out
>
,
public
processor_impl
<
In
,
Out
>
{
public:
// -- member types -----------------------------------------------------------
using
super
=
buffered_observable_impl
<
Out
>
;
using
handle_type
=
processor
<
In
,
Out
>
;
// -- constructors, destructors, and assignment operators --------------------
explicit
buffered_processor_impl
(
coordinator
*
ctx
)
:
super
(
ctx
,
defaults
::
flow
::
buffer_size
)
{
// nop
}
buffered_processor_impl
(
coordinator
*
ctx
,
size_t
max_buffer_size
)
:
super
(
ctx
,
max_buffer_size
)
{
// nop
}
// -- implementation of disposable::impl -------------------------------------
coordinator
*
ctx
()
const
noexcept
override
{
return
super
::
ctx
();
}
void
dispose
()
override
{
if
(
!
this
->
completed_
)
{
if
(
sub_
)
{
sub_
.
cancel
();
sub_
=
nullptr
;
}
super
::
dispose
();
}
}
bool
disposed
()
const
noexcept
override
{
return
super
::
disposed
();
}
void
ref_disposable
()
const
noexcept
override
{
return
super
::
ref_disposable
();
}
void
deref_disposable
()
const
noexcept
override
{
return
super
::
ref_disposable
();
}
// -- implementation of observable<T>::impl ----------------------------------
disposable
subscribe
(
observer
<
Out
>
sink
)
override
{
return
super
::
subscribe
(
std
::
move
(
sink
));
}
void
on_request
(
observer_impl
<
Out
>*
sink
,
size_t
n
)
final
{
super
::
on_request
(
sink
,
n
);
try_fetch_more
();
}
void
on_cancel
(
observer_impl
<
Out
>*
sink
)
final
{
super
::
on_cancel
(
sink
);
try_fetch_more
();
}
// -- implementation of observer<T>::impl ------------------------------------
void
on_subscribe
(
subscription
sub
)
override
{
if
(
sub_
)
{
sub
.
cancel
();
}
else
{
sub_
=
std
::
move
(
sub
);
in_flight_
=
this
->
desired_capacity_
;
sub_
.
request
(
in_flight_
);
}
}
void
on_next
(
span
<
const
In
>
items
)
final
{
CAF_ASSERT
(
in_flight_
>=
items
.
size
());
in_flight_
-=
items
.
size
();
do_on_next
(
items
);
this
->
try_push
();
try_fetch_more
();
}
void
on_complete
()
override
{
sub_
=
nullptr
;
this
->
shutdown
();
}
void
on_error
(
const
error
&
what
)
override
{
sub_
=
nullptr
;
this
->
abort
(
what
);
}
// -- overrides for buffered_observable_impl ---------------------------------
void
shutdown
()
override
{
super
::
shutdown
();
cancel_subscription
();
}
void
abort
(
const
error
&
reason
)
override
{
super
::
abort
(
reason
);
cancel_subscription
();
}
protected:
subscription
sub_
;
size_t
in_flight_
=
0
;
private:
void
cancel_subscription
()
{
if
(
sub_
)
{
sub_
.
cancel
();
sub_
=
nullptr
;
}
}
void
try_fetch_more
()
{
if
(
sub_
)
{
auto
abs
=
in_flight_
+
this
->
buf_
.
size
();
if
(
this
->
desired_capacity_
>
abs
)
{
auto
new_demand
=
this
->
desired_capacity_
-
abs
;
in_flight_
+=
new_demand
;
sub_
.
request
(
new_demand
);
}
}
}
/// Transforms input items to outputs.
virtual
void
do_on_next
(
span
<
const
In
>
items
)
=
0
;
};
/// Broadcasts its input to all observers without modifying it.
template
<
class
T
>
class
broadcaster_impl
:
public
buffered_processor_impl
<
T
,
T
>
{
public:
using
super
=
buffered_processor_impl
<
T
,
T
>
;
using
super
::
super
;
private:
void
do_on_next
(
span
<
const
T
>
items
)
override
{
this
->
append_to_buf
(
items
.
begin
(),
items
.
end
());
}
};
template
<
class
T
>
using
broadcaster_impl_ptr
=
intrusive_ptr
<
broadcaster_impl
<
T
>>
;
// -- transformation -----------------------------------------------------------
template
<
class
...
Steps
>
struct
transform_processor_oracle
;
template
<
class
Step
>
struct
transform_processor_oracle
<
Step
>
{
using
type
=
typename
Step
::
output_type
;
};
template
<
class
Step1
,
class
Step2
,
class
...
Steps
>
struct
transform_processor_oracle
<
Step1
,
Step2
,
Steps
...
>
:
transform_processor_oracle
<
Step2
,
Steps
...
>
{};
template
<
class
...
Steps
>
using
transform_processor_output_type_t
=
typename
transform_processor_oracle
<
Steps
...
>::
type
;
/// 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
<
transform_processor_output_type_t
<
Step
,
Steps
...
>>
{
public:
using
input_type
=
typename
Step
::
input_type
;
using
output_type
=
transform_processor_output_type_t
<
Step
,
Steps
...
>
;
class
impl
:
public
buffered_processor_impl
<
input_type
,
output_type
>
{
public:
using
super
=
buffered_processor_impl
<
input_type
,
output_type
>
;
template
<
class
...
Ts
>
explicit
impl
(
coordinator
*
ctx
,
Ts
&&
...
steps
)
:
super
(
ctx
),
steps
(
std
::
forward
<
Ts
>
(
steps
)...)
{
// nop
}
std
::
tuple
<
Step
,
Steps
...
>
steps
;
private:
void
do_on_next
(
span
<
const
input_type
>
items
)
override
{
auto
f
=
[
this
,
items
](
auto
&
step
,
auto
&
...
steps
)
{
term_step
<
output_type
>
term
{
this
};
for
(
auto
&&
item
:
items
)
if
(
!
step
.
on_next
(
item
,
steps
...,
term
))
return
;
};
std
::
apply
(
f
,
steps
);
}
void
do_on_complete
()
override
{
auto
f
=
[
this
](
auto
&
step
,
auto
&
...
steps
)
{
term_step
<
output_type
>
term
{
this
};
step
.
on_complete
(
steps
...,
term
);
};
std
::
apply
(
f
,
steps
);
}
void
do_on_error
(
const
error
&
what
)
override
{
auto
f
=
[
this
,
&
what
](
auto
&
step
,
auto
&
...
steps
)
{
term_step
<
output_type
>
term
{
this
};
step
.
on_error
(
what
,
steps
...,
term
);
};
std
::
apply
(
f
,
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
do_on_complete
(
F
f
)
{
return
std
::
move
(
*
this
)
//
.
transform
(
on_complete_step
<
output_type
,
F
>
{
std
::
move
(
f
)});
}
template
<
class
F
>
auto
do_on_error
(
F
f
)
{
return
std
::
move
(
*
this
)
//
.
transform
(
on_error_step
<
output_type
,
F
>
{
std
::
move
(
f
)});
}
template
<
class
F
>
auto
do_finally
(
F
f
)
{
return
std
::
move
(
*
this
)
//
.
transform
(
finally_step
<
output_type
,
F
>
{
std
::
move
(
f
)});
}
observable
<
output_type
>
as_observable
()
&&
override
{
auto
pimpl
=
make_counted
<
impl
>
(
source_
.
ptr
()
->
ctx
(),
std
::
move
(
steps_
));
auto
res
=
pimpl
->
as_observable
();
source_
.
subscribe
(
observer
<
input_type
>
{
std
::
move
(
pimpl
)});
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 output 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 output 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 output 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
(
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 -----------------------------------------------------
/// Combines items from any number of observables.
template
<
class
T
>
class
merger_impl
:
public
buffered_observable_impl
<
T
>
{
public:
using
super
=
buffered_observable_impl
<
T
>
;
using
super
::
super
;
void
concat_mode
(
bool
new_value
)
{
flags_
.
concat_mode
=
new_value
;
}
class
forwarder
;
friend
class
forwarder
;
class
forwarder
:
public
ref_counted
,
public
observer_impl
<
T
>
{
public:
CAF_INTRUSIVE_PTR_FRIENDS
(
forwarder
)
explicit
forwarder
(
intrusive_ptr
<
merger_impl
>
parent
)
:
parent
(
std
::
move
(
parent
))
{
// nop
}
void
on_complete
()
override
{
if
(
sub
)
{
sub
=
nullptr
;
parent
->
forwarder_completed
(
this
);
parent
=
nullptr
;
}
}
void
on_error
(
const
error
&
what
)
override
{
if
(
sub
)
{
sub
=
nullptr
;
parent
->
forwarder_failed
(
this
,
what
);
parent
=
nullptr
;
}
}
void
on_subscribe
(
subscription
new_sub
)
override
{
if
(
!
sub
)
{
sub
=
std
::
move
(
new_sub
);
parent
->
forwarder_subscribed
(
this
,
sub
);
}
else
{
new_sub
.
cancel
();
}
}
void
on_next
(
span
<
const
T
>
items
)
override
{
if
(
parent
)
parent
->
on_batch
(
async
::
make_batch
(
items
),
this
);
}
void
dispose
()
override
{
on_complete
();
}
bool
disposed
()
const
noexcept
override
{
return
!
parent
;
}
void
ref_disposable
()
const
noexcept
final
{
this
->
ref
();
}
void
deref_disposable
()
const
noexcept
final
{
this
->
deref
();
}
intrusive_ptr
<
merger_impl
>
parent
;
subscription
sub
;
};
explicit
merger_impl
(
coordinator
*
ctx
)
:
super
(
ctx
,
defaults
::
flow
::
batch_size
)
{
// nop
}
disposable
add
(
observable
<
T
>
source
,
intrusive_ptr
<
forwarder
>
fwd
)
{
forwarders_
.
emplace_back
(
fwd
);
return
source
.
subscribe
(
observer
<
T
>
{
std
::
move
(
fwd
)});
}
template
<
class
Observable
>
disposable
add
(
Observable
source
)
{
return
add
(
std
::
move
(
source
).
as_observable
(),
make_counted
<
forwarder
>
(
this
));
}
bool
done
()
const
noexcept
override
{
return
super
::
done
()
&&
inputs_
.
empty
()
&&
forwarders_
.
empty
();
}
void
dispose
()
override
{
inputs_
.
clear
();
std
::
vector
<
fwd_ptr
>
fwds
;
fwds
.
swap
(
forwarders_
);
for
(
auto
&
fwd
:
fwds
)
fwd
->
dispose
();
super
::
dispose
();
}
void
cancel_inputs
()
{
if
(
!
this
->
completed_
)
{
std
::
vector
<
fwd_ptr
>
fwds
;
fwds
.
swap
(
forwarders_
);
for
(
auto
&
fwd
:
fwds
)
{
if
(
auto
&
sub
=
fwd
->
sub
)
{
sub
.
cancel
();
sub
=
nullptr
;
}
}
this
->
shutdown
();
}
}
bool
disposed
()
const
noexcept
override
{
return
forwarders_
.
empty
()
&&
super
::
disposed
();
}
void
delay_error
(
bool
value
)
{
flags_
.
delay_error
=
value
;
}
void
shutdown_on_last_complete
(
bool
value
)
{
flags_
.
shutdown_on_last_complete
=
value
;
if
(
value
&&
done
())
this
->
shutdown
();
}
void
on_error
(
const
error
&
reason
)
{
if
(
!
flags_
.
delay_error
)
{
abort
(
reason
);
return
;
}
if
(
!
delayed_error_
)
delayed_error_
=
reason
;
}
protected:
void
abort
(
const
error
&
reason
)
override
{
super
::
abort
(
reason
);
inputs_
.
clear
();
forwarders_
.
clear
();
}
private:
using
fwd_ptr
=
intrusive_ptr
<
forwarder
>
;
void
pull
(
size_t
n
)
override
{
while
(
n
>
0
&&
!
inputs_
.
empty
())
{
auto
&
input
=
inputs_
[
0
];
auto
m
=
std
::
min
(
input
.
buf
.
size
()
-
input
.
offset
,
n
);
CAF_ASSERT
(
m
>
0
);
auto
items
=
input
.
buf
.
template
items
<
T
>().
subspan
(
input
.
offset
,
m
);
this
->
append_to_buf
(
items
.
begin
(),
items
.
end
());
if
(
m
+
input
.
offset
==
input
.
buf
.
size
())
{
if
(
auto
&
sub
=
input
.
src
->
sub
)
sub
.
request
(
input
.
buf
.
size
());
inputs_
.
erase
(
inputs_
.
begin
());
}
else
{
input
.
offset
+=
m
;
}
n
-=
m
;
}
}
void
on_batch
(
async
::
batch
buf
,
fwd_ptr
src
)
{
inputs_
.
emplace_back
(
buf
,
src
);
this
->
try_push
();
}
void
forwarder_subscribed
(
forwarder
*
ptr
,
subscription
&
sub
)
{
if
(
!
flags_
.
concat_mode
||
(
!
forwarders_
.
empty
()
&&
forwarders_
[
0
]
==
ptr
))
sub
.
request
(
defaults
::
flow
::
buffer_size
);
}
void
forwarder_failed
(
forwarder
*
ptr
,
const
error
&
reason
)
{
if
(
!
flags_
.
delay_error
)
{
abort
(
reason
);
return
;
}
if
(
!
delayed_error_
)
delayed_error_
=
reason
;
forwarder_completed
(
ptr
);
}
void
forwarder_completed
(
forwarder
*
ptr
)
{
auto
is_ptr
=
[
ptr
](
auto
&
x
)
{
return
x
==
ptr
;
};
auto
i
=
std
::
find_if
(
forwarders_
.
begin
(),
forwarders_
.
end
(),
is_ptr
);
if
(
i
!=
forwarders_
.
end
())
{
forwarders_
.
erase
(
i
);
if
(
forwarders_
.
empty
())
{
if
(
flags_
.
shutdown_on_last_complete
)
{
if
(
delayed_error_
)
this
->
abort
(
delayed_error_
);
else
this
->
shutdown
();
}
}
else
if
(
flags_
.
concat_mode
)
{
if
(
auto
&
sub
=
forwarders_
.
front
()
->
sub
)
sub
.
request
(
defaults
::
flow
::
buffer_size
);
// else: not subscribed yet, so forwarder_subscribed calls sub.request
}
}
}
struct
input_t
{
size_t
offset
=
0
;
async
::
batch
buf
;
fwd_ptr
src
;
input_t
(
async
::
batch
content
,
fwd_ptr
source
)
:
buf
(
std
::
move
(
content
)),
src
(
std
::
move
(
source
))
{
// nop
}
};
struct
flags_t
{
bool
delay_error
:
1
;
bool
shutdown_on_last_complete
:
1
;
bool
concat_mode
:
1
;
flags_t
()
:
delay_error
(
false
),
shutdown_on_last_complete
(
true
),
concat_mode
(
false
)
{
// nop
}
};
std
::
vector
<
input_t
>
inputs_
;
std
::
vector
<
fwd_ptr
>
forwarders_
;
flags_t
flags_
;
error
delayed_error_
;
};
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
)
:
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
);
merger_
->
on_error
(
what
);
merger_
=
nullptr
;
}
}
void
on_subscribe
(
subscription
sub
)
override
{
if
(
!
sub_
&&
merger_
)
{
sub_
=
std
::
move
(
sub
);
sub_
.
request
(
10
);
}
else
{
sub
.
cancel
();
}
}
void
on_next
(
span
<
const
T
>
observables
)
override
{
if
(
sub_
)
{
for
(
const
auto
&
x
:
observables
)
merger_
->
add
(
map_
(
x
).
as_observable
());
sub_
.
request
(
observables
.
size
());
}
}
observable
<
inner_type
>
merger
()
{
return
observable
<
inner_type
>
{
merger_
};
}
auto
&
merger_ptr
()
{
return
merger_
;
}
private:
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::concat_map ---------------------------------------------------
template
<
class
T
>
template
<
class
F
>
auto
observable
<
T
>::
concat_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
));
obs
->
merger_ptr
()
->
concat_mode
(
true
);
pimpl_
->
subscribe
(
obs
->
as_observer
());
return
obs
->
merger
();
}
// -- observable::to_resource --------------------------------------------------
/// Reads from an observable buffer and emits the consumed items.
/// @note Only supports a single observer.
template
<
class
Buffer
>
class
observable_buffer_impl
:
public
ref_counted
,
public
observable_impl
<
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
<
value_type
>
;
// -- friends ----------------------------------------------------------------
CAF_INTRUSIVE_PTR_FRIENDS
(
observable_buffer_impl
)
// -- constructors, destructors, and assignment operators --------------------
observable_buffer_impl
(
coordinator
*
ctx
,
buffer_ptr
buf
)
:
ctx_
(
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
()
{
this
->
ctx
()
->
deref_coordinator
();
}
// -- implementation of disposable::impl -------------------------------------
coordinator
*
ctx
()
const
noexcept
final
{
return
ctx_
;
}
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
;
}
void
ref_disposable
()
const
noexcept
override
{
this
->
ref
();
}
void
deref_disposable
()
const
noexcept
override
{
this
->
deref
();
}
// -- implementation of observable<T>::impl ----------------------------------
void
on_request
(
observer_impl
<
value_type
>*
,
size_t
n
)
override
{
CAF_LOG_TRACE
(
CAF_ARG
(
n
));
demand_
+=
n
;
if
(
demand_
==
n
)
pull
();
}
void
on_cancel
(
observer_impl
<
value_type
>*
)
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 consumer ---------------------------------------------
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_
||
demand_
==
0
)
return
;
pulling_
=
true
;
auto
fin
=
buf_
->
consume
(
async
::
prioritize_errors
,
demand_
,
[
this
](
span
<
const
value_type
>
items
)
{
CAF_LOG_TRACE
(
CAF_ARG
(
items
));
CAF_ASSERT
(
!
items
.
empty
());
CAF_ASSERT
(
demand_
>=
items
.
empty
());
demand_
-=
items
.
size
();
dst_
.
on_next
(
items
);
},
[
this
](
const
error
&
what
)
{
if
(
dst_
)
{
dst_
.
on_error
(
what
);
dst_
=
nullptr
;
}
buf_
=
nullptr
;
});
pulling_
=
false
;
if
(
fin
&&
buf_
)
{
buf_
=
nullptr
;
if
(
dst_
)
{
dst_
.
on_complete
();
dst_
=
nullptr
;
}
}
}
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
>
async
::
consumer_resource
<
T
>
observable
<
T
>::
to_resource
(
size_t
buffer_size
,
size_t
min_request_size
)
{
using
buffer_type
=
async
::
bounded_buffer
<
T
>
;
auto
buf
=
make_counted
<
buffer_type
>
(
buffer_size
,
min_request_size
);
auto
up
=
make_counted
<
buffer_writer_impl
<
buffer_type
>>
(
pimpl_
->
ctx
(),
buf
);
buf
->
set_producer
(
up
);
subscribe
(
up
->
as_observer
());
return
async
::
consumer_resource
<
T
>
{
std
::
move
(
buf
)};
}
// -- observable::observe_on ---------------------------------------------------
template
<
class
T
>
observable
<
T
>
observable
<
T
>::
observe_on
(
coordinator
*
other
,
size_t
buffer_size
,
size_t
min_request_size
)
{
using
buffer_type
=
async
::
bounded_buffer
<
T
>
;
auto
buf
=
make_counted
<
buffer_type
>
(
buffer_size
,
min_request_size
);
auto
up
=
make_counted
<
buffer_writer_impl
<
buffer_type
>>
(
pimpl_
->
ctx
(),
buf
);
auto
down
=
make_counted
<
observable_buffer_impl
<
buffer_type
>>
(
other
,
buf
);
buf
->
set_producer
(
up
);
buf
->
set_consumer
(
down
);
subscribe
(
up
->
as_observer
());
auto
hdl
=
observable
<
T
>
{
std
::
move
(
down
)};
pimpl_
->
ctx
()
->
watch
(
hdl
.
as_disposable
());
return
hdl
;
}
// -- observable::subscribe ----------------------------------------------------
template
<
class
T
>
disposable
observable
<
T
>::
subscribe
(
async
::
producer_resource
<
T
>
resource
)
{
using
buffer_type
=
typename
async
::
consumer_resource
<
T
>::
buffer_type
;
using
adapter_type
=
buffer_writer_impl
<
buffer_type
>
;
if
(
auto
buf
=
resource
.
try_open
())
{
CAF_LOG_DEBUG
(
"subscribe producer resource to flow"
);
auto
adapter
=
make_counted
<
adapter_type
>
(
pimpl_
->
ctx
(),
buf
);
buf
->
set_producer
(
adapter
);
auto
obs
=
adapter
->
as_observer
();
pimpl_
->
ctx
()
->
watch
(
obs
.
as_disposable
());
return
subscribe
(
std
::
move
(
obs
));
}
else
{
CAF_LOG_DEBUG
(
"failed to open producer resource"
);
return
{};
}
}
}
// namespace caf::flow
libcaf_core/caf/flow/observable_builder.hpp
0 → 100644
View file @
4c68deb8
// 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/async/bounded_buffer.hpp"
#include "caf/defaults.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/observable.hpp"
namespace
caf
::
flow
{
template
<
class
Container
>
class
container_source
{
public:
using
output_type
=
typename
Container
::
value_type
;
explicit
container_source
(
Container
&&
values
)
:
values_
(
std
::
move
(
values
))
{
pos_
=
values_
.
begin
();
}
container_source
(
container_source
&&
)
=
default
;
container_source
&
operator
=
(
container_source
&&
)
=
default
;
container_source
()
=
delete
;
container_source
(
const
container_source
&
)
=
delete
;
container_source
&
operator
=
(
const
container_source
&
)
=
delete
;
template
<
class
Step
,
class
...
Steps
>
void
pull
(
size_t
n
,
Step
&
step
,
Steps
&
...
steps
)
{
while
(
pos_
!=
values_
.
end
()
&&
n
>
0
)
{
if
(
!
step
.
on_next
(
*
pos_
++
,
steps
...))
return
;
--
n
;
}
if
(
pos_
==
values_
.
end
())
step
.
on_complete
(
steps
...);
}
private:
Container
values_
;
typename
Container
::
const_iterator
pos_
;
};
template
<
class
T
>
class
repeater_source
{
public:
using
output_type
=
T
;
explicit
repeater_source
(
T
value
)
:
value_
(
std
::
move
(
value
))
{
// nop
}
repeater_source
(
repeater_source
&&
)
=
default
;
repeater_source
(
const
repeater_source
&
)
=
default
;
repeater_source
&
operator
=
(
repeater_source
&&
)
=
default
;
repeater_source
&
operator
=
(
const
repeater_source
&
)
=
default
;
template
<
class
Step
,
class
...
Steps
>
void
pull
(
size_t
n
,
Step
&
step
,
Steps
&
...
steps
)
{
for
(
size_t
i
=
0
;
i
<
n
;
++
i
)
if
(
!
step
.
on_next
(
value_
,
steps
...))
return
;
}
private:
T
value_
;
};
template
<
class
F
>
class
callable_source
{
public:
using
output_type
=
std
::
decay_t
<
decltype
(
std
::
declval
<
F
&>
()())
>
;
explicit
callable_source
(
F
fn
)
:
fn_
(
std
::
move
(
fn
))
{
// nop
}
callable_source
(
callable_source
&&
)
=
default
;
callable_source
&
operator
=
(
callable_source
&&
)
=
default
;
callable_source
(
const
callable_source
&
)
=
delete
;
callable_source
&
operator
=
(
const
callable_source
&
)
=
delete
;
template
<
class
Step
,
class
...
Steps
>
void
pull
(
size_t
n
,
Step
&
step
,
Steps
&
...
steps
)
{
for
(
size_t
i
=
0
;
i
<
n
;
++
i
)
if
(
!
step
.
on_next
(
fn_
(),
steps
...))
return
;
}
private:
F
fn_
;
};
class
observable_builder
{
public:
friend
class
coordinator
;
observable_builder
(
const
observable_builder
&
)
noexcept
=
default
;
observable_builder
&
operator
=
(
const
observable_builder
&
)
noexcept
=
default
;
template
<
class
T
>
[[
nodiscard
]]
generation
<
repeater_source
<
T
>>
repeat
(
T
value
)
const
;
template
<
class
Container
>
[[
nodiscard
]]
generation
<
container_source
<
Container
>>
from_container
(
Container
values
)
const
;
template
<
class
T
>
[[
nodiscard
]]
auto
just
(
T
value
)
const
;
template
<
class
F
>
[[
nodiscard
]]
generation
<
callable_source
<
F
>>
from_callable
(
F
fn
)
const
;
/// Opens an asynchronous, buffered resource and emits all inputs from the
/// buffer.
template
<
class
T
>
[[
nodiscard
]]
observable
<
T
>
from_resource
(
async
::
consumer_resource
<
T
>
hdl
)
const
;
template
<
class
Pullable
>
[[
nodiscard
]]
generation
<
Pullable
>
lift
(
Pullable
pullable
)
const
;
private:
explicit
observable_builder
(
coordinator
*
ctx
)
:
ctx_
(
ctx
)
{
// nop
}
coordinator
*
ctx_
;
};
// -- generation ---------------------------------------------------------------
template
<
class
Generator
,
class
...
Steps
>
class
generation
final
:
public
observable_def
<
transform_processor_output_type_t
<
Generator
,
Steps
...
>>
{
public:
using
output_type
=
transform_processor_output_type_t
<
Generator
,
Steps
...
>
;
class
impl
:
public
buffered_observable_impl
<
output_type
>
{
public:
using
super
=
buffered_observable_impl
<
output_type
>
;
template
<
class
...
Ts
>
impl
(
coordinator
*
ctx
,
Generator
gen
,
Ts
&&
...
steps
)
:
super
(
ctx
),
gen_
(
std
::
move
(
gen
)),
steps_
(
std
::
forward
<
Ts
>
(
steps
)...)
{
// nop
}
private:
virtual
void
pull
(
size_t
n
)
{
auto
fn
=
[
this
,
n
](
auto
&
...
steps
)
{
term_step
<
output_type
>
term
{
this
};
gen_
.
pull
(
n
,
steps
...,
term
);
};
std
::
apply
(
fn
,
steps_
);
}
Generator
gen_
;
std
::
tuple
<
Steps
...
>
steps_
;
};
template
<
class
...
Ts
>
generation
(
coordinator
*
ctx
,
Generator
gen
,
Ts
&&
...
steps
)
:
ctx_
(
ctx
),
gen_
(
std
::
move
(
gen
)),
steps_
(
std
::
forward
<
Ts
>
(
steps
)...)
{
// nop
}
generation
()
=
delete
;
generation
(
const
generation
&
)
=
delete
;
generation
&
operator
=
(
const
generation
&
)
=
delete
;
generation
(
generation
&&
)
=
default
;
generation
&
operator
=
(
generation
&&
)
=
default
;
/// @copydoc observable::transform
template
<
class
NewStep
>
generation
<
Generator
,
Steps
...,
NewStep
>
transform
(
NewStep
step
)
&&
{
static_assert
(
std
::
is_same_v
<
typename
NewStep
::
input_type
,
output_type
>
,
"step object does not match the output type"
);
return
{
ctx_
,
std
::
move
(
gen_
),
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
Fn
>
auto
map
(
Fn
fn
)
&&
{
return
std
::
move
(
*
this
).
transform
(
map_step
<
Fn
>
{
std
::
move
(
fn
)});
}
observable
<
output_type
>
as_observable
()
&&
override
{
auto
pimpl
=
make_counted
<
impl
>
(
ctx_
,
std
::
move
(
gen_
),
std
::
move
(
steps_
));
return
observable
<
output_type
>
{
std
::
move
(
pimpl
)};
}
private:
coordinator
*
ctx_
;
Generator
gen_
;
std
::
tuple
<
Steps
...
>
steps_
;
};
// -- observable_builder::repeat -----------------------------------------------
template
<
class
T
>
generation
<
repeater_source
<
T
>>
observable_builder
::
repeat
(
T
value
)
const
{
return
{
ctx_
,
repeater_source
<
T
>
{
std
::
move
(
value
)}};
}
// -- observable_builder::from_container ---------------------------------------
template
<
class
Container
>
generation
<
container_source
<
Container
>>
observable_builder
::
from_container
(
Container
values
)
const
{
return
{
ctx_
,
container_source
<
Container
>
{
std
::
move
(
values
)}};
}
// -- observable_builder::just -------------------------------------------------
template
<
class
T
>
auto
observable_builder
::
just
(
T
value
)
const
{
return
repeat
(
std
::
move
(
value
)).
take
(
1
);
}
// -- observable_builder::from_callable ----------------------------------------
template
<
class
F
>
generation
<
callable_source
<
F
>>
observable_builder
::
from_callable
(
F
fn
)
const
{
return
{
ctx_
,
callable_source
<
F
>
{
std
::
move
(
fn
)}};
}
// -- observable_builder::from_resource ----------------------------------------
template
<
class
T
>
observable
<
T
>
observable_builder
::
from_resource
(
async
::
consumer_resource
<
T
>
hdl
)
const
{
using
buffer_type
=
typename
async
::
consumer_resource
<
T
>::
buffer_type
;
using
res_t
=
observable
<
T
>
;
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
res_t
{
make_counted
<
observable_error_impl
<
T
>>
(
ctx_
,
std
::
move
(
err
))};
}
}
// -- observable_builder::lift -------------------------------------------------
template
<
class
Pullable
>
generation
<
Pullable
>
observable_builder
::
lift
(
Pullable
pullable
)
const
{
return
{
ctx_
,
std
::
move
(
pullable
)};
}
}
// namespace caf::flow
libcaf_core/caf/flow/observer.hpp
0 → 100644
View file @
4c68deb8
// 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/async/batch.hpp"
#include "caf/async/producer.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/disposable.hpp"
#include "caf/error.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/subscription.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/logger.hpp"
#include "caf/make_counted.hpp"
#include "caf/ref_counted.hpp"
#include "caf/span.hpp"
#include "caf/unit.hpp"
namespace
caf
::
flow
{
/// Handle to a consumer of items.
template
<
class
T
>
class
observer
{
public:
/// Internal interface of an `observer`.
class
impl
:
public
disposable
::
impl
{
public:
using
input_type
=
T
;
virtual
void
on_subscribe
(
subscription
sub
)
=
0
;
virtual
void
on_next
(
span
<
const
T
>
items
)
=
0
;
virtual
void
on_complete
()
=
0
;
virtual
void
on_error
(
const
error
&
what
)
=
0
;
observer
as_observer
()
{
return
observer
{
intrusive_ptr
<
impl
>
(
this
)};
}
};
using
input_type
=
T
;
explicit
observer
(
intrusive_ptr
<
impl
>
pimpl
)
noexcept
:
pimpl_
(
std
::
move
(
pimpl
))
{
// nop
}
observer
&
operator
=
(
std
::
nullptr_t
)
noexcept
{
pimpl_
.
reset
();
return
*
this
;
}
observer
()
noexcept
=
default
;
observer
(
observer
&&
)
noexcept
=
default
;
observer
(
const
observer
&
)
noexcept
=
default
;
observer
&
operator
=
(
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()`
void
on_complete
()
{
pimpl_
->
on_complete
();
}
/// @pre `valid()`
void
on_error
(
const
error
&
what
)
{
pimpl_
->
on_error
(
what
);
}
/// @pre `valid()`
void
on_subscribe
(
subscription
sub
)
{
pimpl_
->
on_subscribe
(
std
::
move
(
sub
));
}
/// @pre `valid()`
void
on_batch
(
const
async
::
batch
&
buf
)
{
pimpl_
->
on_batch
(
buf
);
}
/// @pre `valid()`
void
on_next
(
span
<
const
T
>
items
)
{
pimpl_
->
on_next
(
items
);
}
/// Creates a new observer from `Impl`.
template
<
class
Impl
,
class
...
Ts
>
[[
nodiscard
]]
static
observer
make
(
Ts
&&
...
xs
)
{
static_assert
(
std
::
is_base_of_v
<
impl
,
Impl
>
);
return
observer
{
make_counted
<
Impl
>
(
std
::
forward
<
Ts
>
(
xs
)...)};
}
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
(
observer
&
other
)
{
pimpl_
.
swap
(
other
.
pimpl_
);
}
private:
intrusive_ptr
<
impl
>
pimpl_
;
};
template
<
class
T
>
using
observer_impl
=
typename
observer
<
T
>::
impl
;
// -- writing observed values to a buffer --------------------------------------
/// Writes observed values to a bounded buffer.
template
<
class
Buffer
>
class
buffer_writer_impl
:
public
ref_counted
,
public
observer_impl
<
typename
Buffer
::
value_type
>
,
public
async
::
producer
{
public:
using
buffer_ptr
=
intrusive_ptr
<
Buffer
>
;
using
value_type
=
typename
Buffer
::
value_type
;
CAF_INTRUSIVE_PTR_FRIENDS
(
buffer_writer_impl
)
buffer_writer_impl
(
coordinator
*
ctx
,
buffer_ptr
buf
)
:
ctx_
(
ctx
),
buf_
(
std
::
move
(
buf
))
{
CAF_ASSERT
(
ctx_
!=
nullptr
);
CAF_ASSERT
(
buf_
!=
nullptr
);
}
void
on_consumer_ready
()
override
{
// nop
}
void
on_consumer_cancel
()
override
{
CAF_LOG_TRACE
(
""
);
ctx_
->
schedule_fn
([
ptr
{
strong_ptr
()}]
{
CAF_LOG_TRACE
(
""
);
ptr
->
on_cancel
();
});
}
void
on_consumer_demand
(
size_t
demand
)
override
{
CAF_LOG_TRACE
(
CAF_ARG
(
demand
));
ctx_
->
schedule_fn
([
ptr
{
strong_ptr
()},
demand
]
{
//
CAF_LOG_TRACE
(
CAF_ARG
(
demand
));
ptr
->
on_demand
(
demand
);
});
}
void
ref_disposable
()
const
noexcept
final
{
this
->
ref
();
}
void
deref_disposable
()
const
noexcept
final
{
this
->
deref
();
}
void
ref_producer
()
const
noexcept
final
{
this
->
ref
();
}
void
deref_producer
()
const
noexcept
final
{
this
->
deref
();
}
void
on_next
(
span
<
const
value_type
>
items
)
override
{
CAF_LOG_TRACE
(
CAF_ARG
(
items
));
if
(
buf_
)
buf_
->
push
(
items
);
}
void
on_complete
()
override
{
CAF_LOG_TRACE
(
""
);
if
(
buf_
)
{
buf_
->
close
();
buf_
=
nullptr
;
sub_
=
nullptr
;
}
}
void
on_error
(
const
error
&
what
)
override
{
CAF_LOG_TRACE
(
CAF_ARG
(
what
));
if
(
buf_
)
{
buf_
->
abort
(
what
);
buf_
=
nullptr
;
sub_
=
nullptr
;
}
}
void
on_subscribe
(
subscription
sub
)
override
{
CAF_LOG_TRACE
(
""
);
if
(
buf_
&&
!
sub_
)
{
CAF_LOG_DEBUG
(
"add subscription"
);
sub_
=
std
::
move
(
sub
);
sub_
.
request
(
buf_
->
capacity
());
}
else
{
CAF_LOG_DEBUG
(
"already have a subscription"
);
sub
.
cancel
();
}
}
void
dispose
()
override
{
CAF_LOG_TRACE
(
""
);
on_complete
();
}
bool
disposed
()
const
noexcept
override
{
return
buf_
==
nullptr
;
}
private:
void
on_demand
(
size_t
n
)
{
CAF_LOG_TRACE
(
CAF_ARG
(
n
));
if
(
sub_
)
sub_
.
request
(
n
);
}
void
on_cancel
()
{
CAF_LOG_TRACE
(
""
);
if
(
sub_
)
{
sub_
.
cancel
();
sub_
=
nullptr
;
}
buf_
=
nullptr
;
}
intrusive_ptr
<
buffer_writer_impl
>
strong_ptr
()
{
return
{
this
};
}
coordinator_ptr
ctx_
;
buffer_ptr
buf_
;
subscription
sub_
;
};
}
// namespace caf::flow
namespace
caf
::
detail
{
template
<
class
OnNextSignature
>
struct
on_next_trait
;
template
<
class
T
>
struct
on_next_trait
<
void
(
T
)
>
{
using
value_type
=
T
;
template
<
class
F
>
static
void
apply
(
F
&
f
,
span
<
const
T
>
items
)
{
for
(
auto
&&
item
:
items
)
f
(
item
);
}
};
template
<
class
T
>
struct
on_next_trait
<
void
(
const
T
&
)
>
{
using
value_type
=
T
;
template
<
class
F
>
static
void
apply
(
F
&
f
,
span
<
const
T
>
items
)
{
for
(
auto
&&
item
:
items
)
f
(
item
);
}
};
template
<
class
T
>
struct
on_next_trait
<
void
(
span
<
const
T
>
)
>
{
using
value_type
=
T
;
template
<
class
F
>
static
void
apply
(
F
&
f
,
span
<
const
T
>
items
)
{
f
(
items
);
}
};
template
<
class
F
>
using
on_next_trait_t
=
on_next_trait
<
typename
get_callable_trait_t
<
F
>::
fun_sig
>
;
template
<
class
F
>
using
on_next_value_type
=
typename
on_next_trait_t
<
F
>::
value_type
;
template
<
class
OnNext
,
class
OnError
=
unit_t
,
class
OnComplete
=
unit_t
>
class
default_observer_impl
:
public
ref_counted
,
public
flow
::
observer_impl
<
on_next_value_type
<
OnNext
>>
{
public:
static_assert
(
std
::
is_invocable_v
<
OnError
,
const
error
&>
);
static_assert
(
std
::
is_invocable_v
<
OnComplete
>
);
using
input_type
=
on_next_value_type
<
OnNext
>
;
CAF_INTRUSIVE_PTR_FRIENDS
(
default_observer_impl
)
explicit
default_observer_impl
(
OnNext
&&
on_next_fn
)
:
on_next_
(
std
::
move
(
on_next_fn
))
{
// nop
}
default_observer_impl
(
OnNext
&&
on_next_fn
,
OnError
&&
on_error_fn
)
:
on_next_
(
std
::
move
(
on_next_fn
)),
on_error_
(
std
::
move
(
on_error_fn
))
{
// nop
}
default_observer_impl
(
OnNext
&&
on_next_fn
,
OnError
&&
on_error_fn
,
OnComplete
&&
on_complete_fn
)
:
on_next_
(
std
::
move
(
on_next_fn
)),
on_error_
(
std
::
move
(
on_error_fn
)),
on_complete_
(
std
::
move
(
on_complete_fn
))
{
// nop
}
void
ref_disposable
()
const
noexcept
final
{
this
->
ref
();
}
void
deref_disposable
()
const
noexcept
final
{
this
->
deref
();
}
void
on_next
(
span
<
const
input_type
>
items
)
override
{
if
(
!
completed_
)
{
on_next_trait_t
<
OnNext
>::
apply
(
on_next_
,
items
);
sub_
.
request
(
items
.
size
());
}
}
void
on_error
(
const
error
&
what
)
override
{
if
(
!
completed_
)
{
on_error_
(
what
);
sub_
=
nullptr
;
completed_
=
true
;
}
}
void
on_complete
()
override
{
if
(
!
completed_
)
{
on_complete_
();
sub_
=
nullptr
;
completed_
=
true
;
}
}
void
on_subscribe
(
flow
::
subscription
sub
)
override
{
if
(
!
completed_
&&
!
sub_
)
{
sub_
=
std
::
move
(
sub
);
sub_
.
request
(
defaults
::
flow
::
buffer_size
);
}
else
{
sub
.
cancel
();
}
}
void
dispose
()
override
{
if
(
!
completed_
)
{
on_complete_
();
if
(
sub_
)
{
sub_
.
cancel
();
sub_
=
nullptr
;
}
completed_
=
true
;
}
}
bool
disposed
()
const
noexcept
override
{
return
completed_
;
}
private:
bool
completed_
=
false
;
OnNext
on_next_
;
OnError
on_error_
;
OnComplete
on_complete_
;
flow
::
subscription
sub_
;
};
}
// namespace caf::detail
namespace
caf
::
flow
{
template
<
class
OnNext
,
class
OnError
,
class
OnComplete
>
auto
make_observer
(
OnNext
on_next
,
OnError
on_error
,
OnComplete
on_complete
)
{
using
impl_type
=
detail
::
default_observer_impl
<
OnNext
,
OnError
,
OnComplete
>
;
using
input_type
=
typename
impl_type
::
input_type
;
auto
ptr
=
make_counted
<
impl_type
>
(
std
::
move
(
on_next
),
std
::
move
(
on_error
),
std
::
move
(
on_complete
));
return
observer
<
input_type
>
{
std
::
move
(
ptr
)};
}
template
<
class
OnNext
,
class
OnError
>
auto
make_observer
(
OnNext
on_next
,
OnError
on_error
)
{
using
impl_type
=
detail
::
default_observer_impl
<
OnNext
,
OnError
>
;
using
input_type
=
typename
impl_type
::
input_type
;
auto
ptr
=
make_counted
<
impl_type
>
(
std
::
move
(
on_next
),
std
::
move
(
on_error
));
return
observer
<
input_type
>
{
std
::
move
(
ptr
)};
}
template
<
class
OnNext
>
auto
make_observer
(
OnNext
on_next
)
{
using
impl_type
=
detail
::
default_observer_impl
<
OnNext
>
;
using
input_type
=
typename
impl_type
::
input_type
;
auto
ptr
=
make_counted
<
impl_type
>
(
std
::
move
(
on_next
));
return
observer
<
input_type
>
{
std
::
move
(
ptr
)};
}
}
// namespace caf::flow
libcaf_core/caf/flow/scoped_coordinator.hpp
0 → 100644
View file @
4c68deb8
// 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 <condition_variable>
#include <mutex>
#include "caf/flow/coordinator.hpp"
#include "caf/make_counted.hpp"
#include "caf/ref_counted.hpp"
namespace
caf
::
flow
{
class
CAF_CORE_EXPORT
scoped_coordinator
final
:
public
ref_counted
,
public
coordinator
{
public:
void
run
();
void
ref_coordinator
()
const
noexcept
override
;
void
deref_coordinator
()
const
noexcept
override
;
void
schedule
(
action
what
)
override
;
void
post_internally
(
action
what
)
override
;
void
watch
(
disposable
what
)
override
;
friend
void
intrusive_ptr_add_ref
(
const
scoped_coordinator
*
ptr
)
{
ptr
->
ref
();
}
friend
void
intrusive_ptr_release
(
scoped_coordinator
*
ptr
)
{
ptr
->
deref
();
}
static
intrusive_ptr
<
scoped_coordinator
>
make
();
private:
scoped_coordinator
()
=
default
;
void
drop_disposed_flows
();
std
::
vector
<
disposable
>
watched_disposables_
;
mutable
std
::
mutex
mtx_
;
std
::
condition_variable
cv_
;
std
::
vector
<
action
>
actions_
;
};
using
scoped_coordinator_ptr
=
intrusive_ptr
<
scoped_coordinator
>
;
inline
scoped_coordinator_ptr
make_scoped_coordinator
()
{
return
scoped_coordinator
::
make
();
}
}
// namespace caf::flow
libcaf_core/caf/flow/single.hpp
0 → 100644
View file @
4c68deb8
// 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 <algorithm>
#include <utility>
#include <variant>
#include "caf/detail/overload.hpp"
#include "caf/disposable.hpp"
#include "caf/error.hpp"
#include "caf/flow/observable.hpp"
#include "caf/none.hpp"
#include "caf/ref_counted.hpp"
namespace
caf
::
flow
{
/// Similar to an `observable`, but always emits either a single value or an
/// error.
template
<
class
T
>
class
single
{
public:
using
output_type
=
T
;
/// Internal interface of a `single`.
class
impl
:
public
ref_counted
,
public
observable_impl
<
T
>
{
public:
using
super
=
observable_impl
<
T
>
;
CAF_INTRUSIVE_PTR_FRIENDS
(
impl
)
explicit
impl
(
coordinator
*
ctx
)
:
ctx_
(
ctx
)
{
// nop
}
coordinator
*
ctx
()
const
noexcept
override
{
return
ctx_
;
}
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
(
observer_impl
<
T
>*
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
(
make_span
(
&
val
,
1
));
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
(
observer_impl
<
T
>*
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
::
discarded
));
}
bool
disposed
()
const
noexcept
override
{
return
observers_
.
empty
()
&&
!
std
::
holds_alternative
<
none_t
>
(
value_
);
}
void
ref_disposable
()
const
noexcept
final
{
this
->
ref
();
}
void
deref_disposable
()
const
noexcept
final
{
this
->
deref
();
}
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
(
make_span
(
&
ref
,
1
));
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:
coordinator
*
ctx_
;
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
))
{
// nop
}
single
&
operator
=
(
std
::
nullptr_t
)
noexcept
{
pimpl_
.
reset
();
return
*
this
;
}
single
()
noexcept
=
default
;
single
(
single
&&
)
noexcept
=
default
;
single
(
const
single
&
)
noexcept
=
default
;
single
&
operator
=
(
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
()
&&
{
return
observable
<
T
>
{
std
::
move
(
pimpl_
)};
}
observable
<
T
>
as_observable
()
const
&
{
return
observable
<
T
>
{
pimpl_
};
}
void
subscribe
(
observer
<
T
>
what
)
{
if
(
pimpl_
)
pimpl_
->
subscribe
(
std
::
move
(
what
));
else
what
.
on_error
(
make_error
(
sec
::
invalid_observable
));
}
template
<
class
OnSuccess
,
class
OnError
>
void
subscribe
(
OnSuccess
on_success
,
OnError
on_error
)
{
static_assert
(
std
::
is_invocable_v
<
OnSuccess
,
const
T
&>
);
as_observable
().
for_each
(
[
f
{
std
::
move
(
on_success
)}](
span
<
const
T
>
items
)
mutable
{
CAF_ASSERT
(
items
.
size
()
==
1
);
f
(
items
[
0
]);
},
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
{
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
(
single
&
other
)
{
pimpl_
.
swap
(
other
.
pimpl_
);
}
private:
intrusive_ptr
<
impl
>
pimpl_
;
};
template
<
class
T
>
using
single_impl
=
typename
single
<
T
>::
impl
;
template
<
class
T
>
single
<
T
>
make_single
(
coordinator
*
ctx
)
{
return
single
<
T
>
{
make_counted
<
single_impl
<
T
>>
(
ctx
)};
}
}
// namespace caf::flow
libcaf_core/caf/flow/step.hpp
0 → 100644
View file @
4c68deb8
// 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 <type_traits>
#include "caf/detail/type_list.hpp"
#include "caf/detail/type_traits.hpp"
namespace
caf
::
flow
{
template
<
class
T
>
struct
limit_step
{
size_t
remaining
;
using
input_type
=
T
;
using
output_type
=
T
;
template
<
class
Next
,
class
...
Steps
>
bool
on_next
(
const
input_type
&
item
,
Next
&
next
,
Steps
&
...
steps
)
{
if
(
remaining
>
0
)
{
if
(
next
.
on_next
(
item
,
steps
...))
{
if
(
--
remaining
>
0
)
{
return
true
;
}
else
{
next
.
on_complete
(
steps
...);
return
false
;
}
}
}
return
false
;
}
template
<
class
Next
,
class
...
Steps
>
void
on_complete
(
Next
&
next
,
Steps
&
...
steps
)
{
next
.
on_complete
(
steps
...);
}
template
<
class
Next
,
class
...
Steps
>
void
on_error
(
const
error
&
what
,
Next
&
next
,
Steps
&
...
steps
)
{
next
.
on_error
(
what
,
steps
...);
}
};
template
<
class
Predicate
>
struct
filter_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
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
>
struct
map_step
{
using
trait
=
detail
::
get_callable_trait_t
<
Fn
>
;
static_assert
(
!
std
::
is_same_v
<
typename
trait
::
result_type
,
void
>
,
"map functions may not return void"
);
static_assert
(
trait
::
num_args
==
1
,
"map functions must take exactly one argument"
);
using
input_type
=
std
::
decay_t
<
detail
::
tl_head_t
<
typename
trait
::
arg_types
>>
;
using
output_type
=
std
::
decay_t
<
typename
trait
::
result_type
>
;
Fn
fn
;
template
<
class
Next
,
class
...
Steps
>
bool
on_next
(
const
input_type
&
item
,
Next
&
next
,
Steps
&
...
steps
)
{
return
next
.
on_next
(
fn
(
item
),
steps
...);
}
template
<
class
Next
,
class
...
Steps
>
void
on_complete
(
Next
&
next
,
Steps
&
...
steps
)
{
next
.
on_complete
(
steps
...);
}
template
<
class
Next
,
class
...
Steps
>
void
on_error
(
const
error
&
what
,
Next
&
next
,
Steps
&
...
steps
)
{
next
.
on_error
(
what
,
steps
...);
}
};
template
<
class
T
,
class
Fn
>
struct
on_complete_step
{
using
input_type
=
T
;
using
output_type
=
T
;
Fn
fn
;
template
<
class
Next
,
class
...
Steps
>
bool
on_next
(
const
input_type
&
item
,
Next
&
next
,
Steps
&
...
steps
)
{
return
next
.
on_next
(
item
,
steps
...);
}
template
<
class
Next
,
class
...
Steps
>
void
on_complete
(
Next
&
next
,
Steps
&
...
steps
)
{
fn
();
next
.
on_complete
(
steps
...);
}
template
<
class
Next
,
class
...
Steps
>
void
on_error
(
const
error
&
what
,
Next
&
next
,
Steps
&
...
steps
)
{
next
.
on_error
(
what
,
steps
...);
}
};
template
<
class
T
,
class
Fn
>
struct
on_error_step
{
using
input_type
=
T
;
using
output_type
=
T
;
Fn
fn
;
template
<
class
Next
,
class
...
Steps
>
bool
on_next
(
const
input_type
&
item
,
Next
&
next
,
Steps
&
...
steps
)
{
return
next
.
on_next
(
item
,
steps
...);
}
template
<
class
Next
,
class
...
Steps
>
void
on_complete
(
Next
&
next
,
Steps
&
...
steps
)
{
next
.
on_complete
(
steps
...);
}
template
<
class
Next
,
class
...
Steps
>
void
on_error
(
const
error
&
what
,
Next
&
next
,
Steps
&
...
steps
)
{
fn
();
next
.
on_error
(
what
,
steps
...);
}
};
template
<
class
T
,
class
Fn
>
struct
finally_step
{
using
input_type
=
T
;
using
output_type
=
T
;
Fn
fn
;
template
<
class
Next
,
class
...
Steps
>
bool
on_next
(
const
input_type
&
item
,
Next
&
next
,
Steps
&
...
steps
)
{
return
next
.
on_next
(
item
,
steps
...);
}
template
<
class
Next
,
class
...
Steps
>
void
on_complete
(
Next
&
next
,
Steps
&
...
steps
)
{
fn
();
next
.
on_complete
(
steps
...);
}
template
<
class
Next
,
class
...
Steps
>
void
on_error
(
const
error
&
what
,
Next
&
next
,
Steps
&
...
steps
)
{
fn
();
next
.
on_error
(
what
,
steps
...);
}
};
}
// namespace caf::flow
libcaf_core/caf/flow/subscription.hpp
0 → 100644
View file @
4c68deb8
// 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 <cstddef>
#include "caf/detail/core_export.hpp"
#include "caf/disposable.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/ref_counted.hpp"
namespace
caf
::
flow
{
/// Controls the flow of items from publishers to subscribers.
class
CAF_CORE_EXPORT
subscription
{
public:
// -- nested types -----------------------------------------------------------
/// Internal impl of a `disposable`.
class
impl
:
public
disposable
::
impl
{
public:
~
impl
()
override
;
/// Causes the publisher to stop producing items for the subscriber. Any
/// in-flight items may still get dispatched.
virtual
void
cancel
()
=
0
;
/// Signals demand for `n` more items.
virtual
void
request
(
size_t
n
)
=
0
;
void
dispose
()
final
;
};
// -- constructors, destructors, and assignment operators --------------------
explicit
subscription
(
intrusive_ptr
<
impl
>
pimpl
)
noexcept
:
pimpl_
(
std
::
move
(
pimpl
))
{
// nop
}
subscription
&
operator
=
(
std
::
nullptr_t
)
noexcept
{
pimpl_
.
reset
();
return
*
this
;
}
subscription
()
noexcept
=
default
;
subscription
(
subscription
&&
)
noexcept
=
default
;
subscription
(
const
subscription
&
)
noexcept
=
default
;
subscription
&
operator
=
(
subscription
&&
)
noexcept
=
default
;
subscription
&
operator
=
(
const
subscription
&
)
noexcept
=
default
;
// -- demand signaling -------------------------------------------------------
/// @copydoc impl::cancel
void
cancel
()
{
if
(
pimpl_
)
{
pimpl_
->
cancel
();
pimpl_
=
nullptr
;
}
}
/// @copydoc impl::request
/// @pre `valid()`
void
request
(
size_t
n
)
{
pimpl_
->
request
(
n
);
}
// -- properties -------------------------------------------------------------
bool
valid
()
const
noexcept
{
return
pimpl_
!=
nullptr
;
}
explicit
operator
bool
()
const
noexcept
{
return
valid
();
}
bool
operator
!
()
const
noexcept
{
return
!
valid
();
}
impl
*
ptr
()
noexcept
{
return
pimpl_
.
get
();
}
const
impl
*
ptr
()
const
noexcept
{
return
pimpl_
.
get
();
}
intrusive_ptr
<
impl
>
as_intrusive_ptr
()
const
&
noexcept
{
return
pimpl_
;
}
intrusive_ptr
<
impl
>&&
as_intrusive_ptr
()
&&
noexcept
{
return
std
::
move
(
pimpl_
);
}
disposable
as_disposable
()
const
&
noexcept
{
return
disposable
{
pimpl_
};
}
disposable
as_disposable
()
&&
noexcept
{
return
disposable
{
std
::
move
(
pimpl_
)};
}
// -- swapping ---------------------------------------------------------------
void
swap
(
subscription
&
other
)
noexcept
{
pimpl_
.
swap
(
other
.
pimpl_
);
}
private:
intrusive_ptr
<
impl
>
pimpl_
;
};
}
// namespace caf::flow
libcaf_core/caf/fwd.hpp
View file @
4c68deb8
...
...
@@ -381,4 +381,8 @@ using strong_actor_ptr = intrusive_ptr<actor_control_block>;
using
mailbox_element_ptr
=
std
::
unique_ptr
<
mailbox_element
>
;
using
tracing_data_ptr
=
std
::
unique_ptr
<
tracing_data
>
;
// -- shared pointer aliases ---------------------------------------------------
using
shared_action_ptr
=
std
::
shared_ptr
<
callback
<
void
()
>>
;
}
// namespace caf
libcaf_core/caf/intrusive_ptr.hpp
View file @
4c68deb8
...
...
@@ -271,3 +271,22 @@ std::string to_string(const intrusive_ptr<T>& x) {
}
// namespace caf
/// Convenience macro for adding `intrusive_ptr_add_ref` and
/// `intrusive_ptr_release` as free friend functions.
#define CAF_INTRUSIVE_PTR_FRIENDS(class_name) \
friend void intrusive_ptr_add_ref(const class_name* ptr) noexcept { \
ptr->ref(); \
} \
friend void intrusive_ptr_release(const class_name* ptr) noexcept { \
ptr->deref(); \
}
/// Convenience macro for adding `intrusive_ptr_add_ref` and
/// `intrusive_ptr_release` as free friend functions with a custom suffix.
#define CAF_INTRUSIVE_PTR_FRIENDS_SFX(class_name, suffix) \
friend void intrusive_ptr_add_ref(const class_name* ptr) noexcept { \
ptr->ref##suffix(); \
} \
friend void intrusive_ptr_release(const class_name* ptr) noexcept { \
ptr->deref##suffix(); \
}
libcaf_core/caf/ref_counted.hpp
View file @
4c68deb8
...
...
@@ -41,18 +41,16 @@ public:
return
rc_
;
}
friend
void
intrusive_ptr_add_ref
(
const
ref_counted
*
p
)
noexcept
{
p
->
ref
();
}
friend
void
intrusive_ptr_release
(
const
ref_counted
*
p
)
noexcept
{
p
->
deref
();
}
protected:
mutable
std
::
atomic
<
size_t
>
rc_
;
};
/// @relates ref_counted
inline
void
intrusive_ptr_add_ref
(
const
ref_counted
*
p
)
{
p
->
ref
();
}
/// @relates ref_counted
inline
void
intrusive_ptr_release
(
const
ref_counted
*
p
)
{
p
->
deref
();
}
}
// namespace caf
libcaf_core/caf/response_handle.hpp
View file @
4c68deb8
...
...
@@ -8,6 +8,7 @@
#include "caf/actor_traits.hpp"
#include "caf/catch_all.hpp"
#include "caf/flow/fwd.hpp"
#include "caf/message_id.hpp"
#include "caf/none.hpp"
#include "caf/sec.hpp"
...
...
@@ -104,6 +105,18 @@ public:
then
(
std
::
move
(
f
),
[
self
](
error
&
err
)
{
self
->
call_error_handler
(
err
);
});
}
template
<
class
T
>
flow
::
assert_scheduled_actor_hdr_t
<
flow
::
single
<
T
>>
as_single
()
&&
{
static_assert
(
std
::
is_same_v
<
response_type
,
message
>
);
return
self_
->
template
single_from_response
<
T
>(
policy_
);
}
template
<
class
T
>
flow
::
assert_scheduled_actor_hdr_t
<
flow
::
observable
<
T
>>
as_observable
()
&&
{
static_assert
(
std
::
is_same_v
<
response_type
,
message
>
);
return
self_
->
template
single_from_response
<
T
>(
policy_
).
as_observable
();
}
// -- blocking API -----------------------------------------------------------
template
<
class
T
=
traits
,
class
F
=
none_t
,
class
OnError
=
none_t
,
...
...
libcaf_core/caf/scheduled_actor.hpp
View file @
4c68deb8
...
...
@@ -20,8 +20,11 @@
#include "caf/detail/behavior_stack.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/detail/unordered_flat_map.hpp"
#include "caf/disposable.hpp"
#include "caf/error.hpp"
#include "caf/extend.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/fwd.hpp"
#include "caf/fwd.hpp"
#include "caf/inbound_path.hpp"
#include "caf/intrusive/drr_cached_queue.hpp"
...
...
@@ -72,8 +75,14 @@ CAF_CORE_EXPORT skippable_result drop(scheduled_actor*, message&);
/// A cooperatively scheduled, event-based actor implementation.
class
CAF_CORE_EXPORT
scheduled_actor
:
public
local_actor
,
public
resumable
,
public
non_blocking_actor_base
{
public
non_blocking_actor_base
,
public
flow
::
coordinator
{
public:
// -- friends ----------------------------------------------------------------
template
<
class
,
class
>
friend
class
response_handle
;
// -- nested enums -----------------------------------------------------------
/// Categorizes incoming messages.
...
...
@@ -472,6 +481,18 @@ public:
/// Returns the queue of the mailbox that stores `downstream_msg` messages.
downstream_queue
&
get_downstream_queue
();
// -- caf::flow API ----------------------------------------------------------
void
ref_coordinator
()
const
noexcept
override
;
void
deref_coordinator
()
const
noexcept
override
;
void
schedule
(
action
what
)
override
;
void
post_internally
(
action
what
)
override
;
void
watch
(
disposable
what
)
override
;
// -- inbound_path management ------------------------------------------------
/// Creates a new path for incoming stream traffic from `sender`.
...
...
@@ -660,7 +681,8 @@ public:
bool
alive
()
const
noexcept
{
return
!
bhvr_stack_
.
empty
()
||
!
awaited_responses_
.
empty
()
||
!
multiplexed_responses_
.
empty
()
||
!
stream_managers_
.
empty
()
||
!
pending_stream_managers_
.
empty
();
||
!
pending_stream_managers_
.
empty
()
||
!
watched_disposables_
.
empty
();
}
auto
max_batch_delay
()
const
noexcept
{
...
...
@@ -671,6 +693,13 @@ public:
std
::
vector
<
stream_manager
*>
active_stream_managers
();
/// Runs all pending actions.
void
run_actions
();
std
::
vector
<
disposable
>
watched_disposables
()
const
{
return
watched_disposables_
;
}
/// @endcond
protected:
...
...
@@ -735,6 +764,8 @@ protected:
#endif // CAF_ENABLE_EXCEPTIONS
private:
// -- utilities for instrumenting actors -------------------------------------
template
<
class
F
>
intrusive
::
task_result
run_with_metrics
(
mailbox_element
&
x
,
F
body
)
{
if
(
metrics_
.
mailbox_time
)
{
...
...
@@ -752,9 +783,31 @@ private:
}
}
// -- timeout management -----------------------------------------------------
disposable
run_scheduled
(
timestamp
when
,
action
what
);
disposable
run_scheduled
(
actor_clock
::
time_point
when
,
action
what
);
disposable
run_delayed
(
timespan
delay
,
action
what
);
// -- caf::flow bindings -----------------------------------------------------
template
<
class
T
,
class
Policy
>
flow
::
single
<
T
>
single_from_response
(
Policy
&
policy
)
{
return
single_from_response_impl
<
T
>
(
policy
);
}
template
<
class
T
,
class
Policy
>
flow
::
single
<
T
>
single_from_response_impl
(
Policy
&
policy
);
/// Removes any watched object that became disposed since the last update.
void
update_watched_disposables
();
/// Stores actions that the actor executes after processing the current
/// message.
std
::
vector
<
action
>
actions_
;
/// Stores resources that block the actor from terminating.
std
::
vector
<
disposable
>
watched_disposables_
;
};
}
// namespace caf
libcaf_core/caf/scheduled_actor/flow.hpp
0 → 100644
View file @
4c68deb8
// 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/actor.hpp"
#include "caf/disposable.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/observable.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/single.hpp"
#include "caf/scheduled_actor.hpp"
namespace
caf
::
flow
{
template
<
>
struct
has_impl_include
<
scheduled_actor
>
{
static
constexpr
bool
value
=
true
;
};
}
// namespace caf::flow
namespace
caf
{
template
<
class
T
,
class
Policy
>
flow
::
single
<
T
>
scheduled_actor
::
single_from_response_impl
(
Policy
&
policy
)
{
using
output_type
=
T
;
using
impl_type
=
typename
flow
::
single
<
output_type
>::
impl
;
auto
ptr
=
make_counted
<
impl_type
>
(
this
);
policy
.
then
(
this
,
[
this
,
ptr
](
T
&
val
)
{
ptr
->
set_value
(
std
::
move
(
val
));
run_actions
();
},
[
this
,
ptr
](
error
&
err
)
{
ptr
->
set_error
(
std
::
move
(
err
));
run_actions
();
});
return
flow
::
single
<
output_type
>
{
std
::
move
(
ptr
)};
}
}
// namespace caf
libcaf_core/caf/sec.hpp
View file @
4c68deb8
...
...
@@ -166,6 +166,11 @@ enum class sec : uint8_t {
/// Signals that an actor fell behind a periodic action trigger. After raising
/// this error, an @ref actor_clock stops scheduling the action.
action_reschedule_failed
,
/// Attaching to an observable failed because the target is invalid.
invalid_observable
,
/// Signals to a component that is has been discarded by its parent or the
/// consumer/producer that was attached to the component.
discarded
=
70
,
};
// --(rst-sec-end)--
...
...
libcaf_core/caf/type_id.hpp
View file @
4c68deb8
...
...
@@ -406,6 +406,7 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_module, 0)
CAF_ADD_TYPE_ID
(
core_module
,
(
caf
::
open_stream_msg
))
CAF_ADD_TYPE_ID
(
core_module
,
(
caf
::
pec
))
CAF_ADD_TYPE_ID
(
core_module
,
(
caf
::
sec
))
CAF_ADD_TYPE_ID
(
core_module
,
(
caf
::
shared_action_ptr
))
CAF_ADD_TYPE_ID
(
core_module
,
(
caf
::
stream_slots
))
CAF_ADD_TYPE_ID
(
core_module
,
(
caf
::
strong_actor_ptr
))
CAF_ADD_TYPE_ID
(
core_module
,
(
caf
::
timespan
))
...
...
libcaf_core/src/async/batch.cpp
0 → 100644
View file @
4c68deb8
// 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/async/batch.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/deserializer.hpp"
#include "caf/detail/meta_object.hpp"
#include "caf/serializer.hpp"
namespace
caf
::
async
{
// -- batch::data --------------------------------------------------------------
namespace
{
// void dynamic_item_destructor(type_id_t item_type, uint16_t item_size,
// size_t array_size, byte* data_ptr) {
// auto meta = detail::global_meta_object(item_type);
// do {
// meta->destroy(data_ptr);
// data_ptr += item_size;
// --array_size;
// } while (array_size > 0);
// }
}
// namespace
template
<
class
Inspector
>
bool
batch
::
data
::
save
(
Inspector
&
sink
)
const
{
CAF_ASSERT
(
size_
>
0
);
if
(
item_type_
==
invalid_type_id
)
{
sink
.
emplace_error
(
sec
::
unsafe_type
);
return
false
;
}
auto
meta
=
detail
::
global_meta_object
(
item_type_
);
auto
ptr
=
storage_
;
if
(
!
sink
.
begin_sequence
(
size_
))
return
false
;
auto
len
=
size_
;
do
{
if
constexpr
(
std
::
is_same_v
<
Inspector
,
binary_serializer
>
)
{
if
(
!
meta
->
save_binary
(
sink
,
ptr
))
return
false
;
}
else
{
if
(
!
meta
->
save
(
sink
,
ptr
))
return
false
;
}
ptr
+=
item_size_
;
--
len
;
}
while
(
len
>
0
);
return
sink
.
end_sequence
();
}
// -- batch --------------------------------------------------------------------
template
<
class
Inspector
>
bool
batch
::
save_impl
(
Inspector
&
f
)
const
{
if
(
data_
)
return
data_
->
save
(
f
);
else
return
f
.
begin_sequence
(
0
)
&&
f
.
end_sequence
();
}
bool
batch
::
save
(
serializer
&
f
)
const
{
return
save_impl
(
f
);
}
bool
batch
::
save
(
binary_serializer
&
f
)
const
{
return
save_impl
(
f
);
}
template
<
class
Inspector
>
bool
batch
::
load_impl
(
Inspector
&
)
{
// TODO: implement me
return
false
;
/*
auto len = size_t{0};
if (!f.begin_sequence(len))
return false;
if (len == 0) {
data_.reset();
return f.end_sequence();
}
*/
}
bool
batch
::
load
(
deserializer
&
f
)
{
return
load_impl
(
f
);
}
bool
batch
::
load
(
binary_deserializer
&
f
)
{
return
load_impl
(
f
);
}
}
// namespace caf::async
libcaf_core/src/async/consumer.cpp
0 → 100644
View file @
4c68deb8
// 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/async/consumer.hpp"
namespace
caf
::
async
{
consumer
::~
consumer
()
{
// nop
}
}
// namespace caf::async
libcaf_core/src/async/producer.cpp
0 → 100644
View file @
4c68deb8
// 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/async/producer.hpp"
namespace
caf
::
async
{
producer
::~
producer
()
{
// nop
}
}
// namespace caf::async
libcaf_core/src/flow/coordinator.cpp
0 → 100644
View file @
4c68deb8
// 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/coordinator.hpp"
#include "caf/config.hpp"
#include "caf/flow/observable_builder.hpp"
namespace
caf
::
flow
{
coordinator
::~
coordinator
()
{
// nop
}
observable_builder
coordinator
::
make_observable
()
{
return
observable_builder
{
this
};
}
// void coordinator::subscription_impl::request(size_t n) {
// CAF_ASSERT(n != 0);
// if (src_) {
// ctx_->dispatch_request(src_.get(), snk_.get(), n);
// }
// }
//
// void coordinator::subscription_impl::cancel() {
// if (src_) {
// ctx_->dispatch_cancel(src_.get(), snk_.get());
// src_.reset();
// snk_.reset();
// }
// }
//
// bool coordinator::subscription_impl::disposed() const noexcept {
// return src_ == nullptr;
// }
}
// namespace caf::flow
libcaf_core/src/flow/scoped_coordinator.cpp
0 → 100644
View file @
4c68deb8
// 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/scoped_coordinator.hpp"
namespace
caf
::
flow
{
void
scoped_coordinator
::
run
()
{
auto
next
=
[
this
](
bool
blocking
)
{
std
::
unique_lock
guard
{
mtx_
};
if
(
blocking
)
{
while
(
actions_
.
empty
())
cv_
.
wait
(
guard
);
}
else
if
(
actions_
.
empty
())
{
return
action
{};
}
auto
res
=
std
::
move
(
actions_
[
0
]);
actions_
.
erase
(
actions_
.
begin
());
return
res
;
};
for
(;;)
{
auto
hdl
=
next
(
!
watched_disposables_
.
empty
());
if
(
hdl
.
ptr
()
!=
nullptr
)
hdl
.
run
();
else
return
;
}
}
void
scoped_coordinator
::
ref_coordinator
()
const
noexcept
{
ref
();
}
void
scoped_coordinator
::
deref_coordinator
()
const
noexcept
{
deref
();
}
void
scoped_coordinator
::
schedule
(
action
what
)
{
std
::
unique_lock
guard
{
mtx_
};
actions_
.
emplace_back
(
std
::
move
(
what
));
if
(
actions_
.
size
()
==
1
)
cv_
.
notify_all
();
}
void
scoped_coordinator
::
post_internally
(
action
what
)
{
schedule
(
std
::
move
(
what
));
}
void
scoped_coordinator
::
watch
(
disposable
what
)
{
watched_disposables_
.
emplace_back
(
std
::
move
(
what
));
}
intrusive_ptr
<
scoped_coordinator
>
scoped_coordinator
::
make
()
{
return
{
new
scoped_coordinator
,
false
};
}
void
scoped_coordinator
::
drop_disposed_flows
()
{
auto
disposed
=
[](
auto
&
hdl
)
{
return
hdl
.
disposed
();
};
auto
&
xs
=
watched_disposables_
;
if
(
auto
e
=
std
::
remove_if
(
xs
.
begin
(),
xs
.
end
(),
disposed
);
e
!=
xs
.
end
())
xs
.
erase
(
e
,
xs
.
end
());
}
}
// namespace caf::flow
libcaf_core/src/flow/subscription.cpp
0 → 100644
View file @
4c68deb8
// 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/subscription.hpp"
namespace
caf
::
flow
{
subscription
::
impl
::~
impl
()
{
// nop
}
void
subscription
::
impl
::
dispose
()
{
cancel
();
}
}
// namespace caf::flow
libcaf_core/src/init_global_meta_objects.cpp
View file @
4c68deb8
...
...
@@ -10,6 +10,7 @@
#include "caf/actor_control_block.hpp"
#include "caf/actor_system.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/callback.hpp"
#include "caf/config_value.hpp"
#include "caf/downstream_msg.hpp"
#include "caf/error.hpp"
...
...
libcaf_core/src/scheduled_actor.cpp
View file @
4c68deb8
...
...
@@ -231,6 +231,13 @@ bool scheduled_actor::cleanup(error&& fail_state, execution_unit* host) {
stream_managers_
.
clear
();
pending_stream_managers_
.
clear
();
get_downstream_queue
().
cleanup
();
// Cancel any active flow.
while
(
!
watched_disposables_
.
empty
())
{
for
(
auto
&
ptr
:
watched_disposables_
)
ptr
.
dispose
();
watched_disposables_
.
clear
();
run_actions
();
}
// Clear mailbox.
if
(
!
mailbox_
.
closed
())
{
mailbox_
.
close
();
...
...
@@ -452,8 +459,10 @@ proxy_registry* scheduled_actor::proxy_registry_ptr() {
void
scheduled_actor
::
quit
(
error
x
)
{
CAF_LOG_TRACE
(
CAF_ARG
(
x
));
// Make sure repeated calls to quit don't do anything.
if
(
getf
(
is_shutting_down_flag
))
if
(
getf
(
is_shutting_down_flag
))
{
CAF_LOG_DEBUG
(
"already shutting down"
);
return
;
}
// Mark this actor as about-to-die.
setf
(
is_shutting_down_flag
);
// Store shutdown reason.
...
...
@@ -468,6 +477,13 @@ void scheduled_actor::quit(error x) {
set_error_handler
(
silently_ignore
<
error
>
);
// Drop future messages and produce sec::request_receiver_down for requests.
set_default_handler
(
drop_after_quit
);
// Cancel any active flow.
while
(
!
watched_disposables_
.
empty
())
{
for
(
auto
&
ptr
:
watched_disposables_
)
ptr
.
dispose
();
watched_disposables_
.
clear
();
run_actions
();
}
// Tell all streams to shut down.
std
::
vector
<
stream_manager_ptr
>
managers
;
for
(
auto
&
smm
:
{
stream_managers_
,
pending_stream_managers_
})
...
...
@@ -569,6 +585,24 @@ void scheduled_actor::set_stream_timeout(actor_clock::time_point x) {
});
}
// -- caf::flow API ------------------------------------------------------------
void
scheduled_actor
::
ref_coordinator
()
const
noexcept
{
intrusive_ptr_add_ref
(
ctrl
());
}
void
scheduled_actor
::
deref_coordinator
()
const
noexcept
{
intrusive_ptr_release
(
ctrl
());
}
void
scheduled_actor
::
schedule
(
action
what
)
{
enqueue
(
nullptr
,
make_message_id
(),
make_message
(
std
::
move
(
what
)),
nullptr
);
}
void
scheduled_actor
::
post_internally
(
action
what
)
{
actions_
.
emplace_back
(
std
::
move
(
what
));
}
// -- message processing -------------------------------------------------------
void
scheduled_actor
::
add_awaited_response_handler
(
message_id
response_id
,
...
...
@@ -868,8 +902,9 @@ bool scheduled_actor::finalize() {
++
i
;
}
}
// An actor is considered alive as long as it has a behavior and didn't set
// the terminated flag.
// An actor is considered alive as long as it has a behavior, didn't set
// the terminated flag and has no watched flows remaining.
run_actions
();
if
(
alive
())
return
false
;
CAF_LOG_DEBUG
(
"actor has no behavior and is ready for cleanup"
);
...
...
@@ -1187,4 +1222,33 @@ std::vector<stream_manager*> scheduled_actor::active_stream_managers() {
return
result
;
}
// -- scheduling of caf::flow events -------------------------------------------
void
scheduled_actor
::
watch
(
disposable
obj
)
{
CAF_ASSERT
(
obj
.
valid
());
watched_disposables_
.
emplace_back
(
std
::
move
(
obj
));
CAF_LOG_DEBUG
(
"now watching"
<<
watched_disposables_
.
size
()
<<
"disposables"
);
}
void
scheduled_actor
::
run_actions
()
{
if
(
!
actions_
.
empty
())
{
// Note: can't use iterators here since actions may add to the vector.
for
(
auto
index
=
size_t
{
0
};
index
<
actions_
.
size
();
++
index
)
{
auto
f
=
std
::
move
(
actions_
[
index
]);
f
.
run
();
}
actions_
.
clear
();
}
update_watched_disposables
();
}
void
scheduled_actor
::
update_watched_disposables
()
{
auto
disposed
=
[](
auto
&
hdl
)
{
return
hdl
.
disposed
();
};
auto
&
xs
=
watched_disposables_
;
if
(
auto
e
=
std
::
remove_if
(
xs
.
begin
(),
xs
.
end
(),
disposed
);
e
!=
xs
.
end
())
{
xs
.
erase
(
e
,
xs
.
end
());
CAF_LOG_DEBUG
(
"now watching"
<<
xs
.
size
()
<<
"disposables"
);
}
}
}
// namespace caf
libcaf_core/src/sec_strings.cpp
0 → 100644
View file @
4c68deb8
// clang-format off
// DO NOT EDIT: this file is auto-generated by caf-generate-enum-strings.
// Run the target update-enum-strings if this file is out of sync.
#include "caf/config.hpp"
#include "caf/string_view.hpp"
CAF_PUSH_DEPRECATED_WARNING
#include "caf/sec.hpp"
#include <string>
namespace
caf
{
std
::
string
to_string
(
sec
x
)
{
switch
(
x
)
{
default:
return
"???"
;
case
sec
:
:
none
:
return
"caf::sec::none"
;
case
sec
:
:
unexpected_message
:
return
"caf::sec::unexpected_message"
;
case
sec
:
:
unexpected_response
:
return
"caf::sec::unexpected_response"
;
case
sec
:
:
request_receiver_down
:
return
"caf::sec::request_receiver_down"
;
case
sec
:
:
request_timeout
:
return
"caf::sec::request_timeout"
;
case
sec
:
:
no_such_group_module
:
return
"caf::sec::no_such_group_module"
;
case
sec
:
:
no_actor_published_at_port
:
return
"caf::sec::no_actor_published_at_port"
;
case
sec
:
:
unexpected_actor_messaging_interface
:
return
"caf::sec::unexpected_actor_messaging_interface"
;
case
sec
:
:
state_not_serializable
:
return
"caf::sec::state_not_serializable"
;
case
sec
:
:
unsupported_sys_key
:
return
"caf::sec::unsupported_sys_key"
;
case
sec
:
:
unsupported_sys_message
:
return
"caf::sec::unsupported_sys_message"
;
case
sec
:
:
disconnect_during_handshake
:
return
"caf::sec::disconnect_during_handshake"
;
case
sec
:
:
cannot_forward_to_invalid_actor
:
return
"caf::sec::cannot_forward_to_invalid_actor"
;
case
sec
:
:
no_route_to_receiving_node
:
return
"caf::sec::no_route_to_receiving_node"
;
case
sec
:
:
failed_to_assign_scribe_from_handle
:
return
"caf::sec::failed_to_assign_scribe_from_handle"
;
case
sec
:
:
failed_to_assign_doorman_from_handle
:
return
"caf::sec::failed_to_assign_doorman_from_handle"
;
case
sec
:
:
cannot_close_invalid_port
:
return
"caf::sec::cannot_close_invalid_port"
;
case
sec
:
:
cannot_connect_to_node
:
return
"caf::sec::cannot_connect_to_node"
;
case
sec
:
:
cannot_open_port
:
return
"caf::sec::cannot_open_port"
;
case
sec
:
:
network_syscall_failed
:
return
"caf::sec::network_syscall_failed"
;
case
sec
:
:
invalid_argument
:
return
"caf::sec::invalid_argument"
;
case
sec
:
:
invalid_protocol_family
:
return
"caf::sec::invalid_protocol_family"
;
case
sec
:
:
cannot_publish_invalid_actor
:
return
"caf::sec::cannot_publish_invalid_actor"
;
case
sec
:
:
cannot_spawn_actor_from_arguments
:
return
"caf::sec::cannot_spawn_actor_from_arguments"
;
case
sec
:
:
end_of_stream
:
return
"caf::sec::end_of_stream"
;
case
sec
:
:
no_context
:
return
"caf::sec::no_context"
;
case
sec
:
:
unknown_type
:
return
"caf::sec::unknown_type"
;
case
sec
:
:
no_proxy_registry
:
return
"caf::sec::no_proxy_registry"
;
case
sec
:
:
runtime_error
:
return
"caf::sec::runtime_error"
;
case
sec
:
:
remote_linking_failed
:
return
"caf::sec::remote_linking_failed"
;
case
sec
:
:
cannot_add_upstream
:
return
"caf::sec::cannot_add_upstream"
;
case
sec
:
:
upstream_already_exists
:
return
"caf::sec::upstream_already_exists"
;
case
sec
:
:
invalid_upstream
:
return
"caf::sec::invalid_upstream"
;
case
sec
:
:
cannot_add_downstream
:
return
"caf::sec::cannot_add_downstream"
;
case
sec
:
:
downstream_already_exists
:
return
"caf::sec::downstream_already_exists"
;
case
sec
:
:
invalid_downstream
:
return
"caf::sec::invalid_downstream"
;
case
sec
:
:
no_downstream_stages_defined
:
return
"caf::sec::no_downstream_stages_defined"
;
case
sec
:
:
stream_init_failed
:
return
"caf::sec::stream_init_failed"
;
case
sec
:
:
invalid_stream_state
:
return
"caf::sec::invalid_stream_state"
;
case
sec
:
:
unhandled_stream_error
:
return
"caf::sec::unhandled_stream_error"
;
case
sec
:
:
bad_function_call
:
return
"caf::sec::bad_function_call"
;
case
sec
:
:
feature_disabled
:
return
"caf::sec::feature_disabled"
;
case
sec
:
:
cannot_open_file
:
return
"caf::sec::cannot_open_file"
;
case
sec
:
:
socket_invalid
:
return
"caf::sec::socket_invalid"
;
case
sec
:
:
socket_disconnected
:
return
"caf::sec::socket_disconnected"
;
case
sec
:
:
socket_operation_failed
:
return
"caf::sec::socket_operation_failed"
;
case
sec
:
:
unavailable_or_would_block
:
return
"caf::sec::unavailable_or_would_block"
;
case
sec
:
:
incompatible_versions
:
return
"caf::sec::incompatible_versions"
;
case
sec
:
:
incompatible_application_ids
:
return
"caf::sec::incompatible_application_ids"
;
case
sec
:
:
malformed_basp_message
:
return
"caf::sec::malformed_basp_message"
;
case
sec
:
:
serializing_basp_payload_failed
:
return
"caf::sec::serializing_basp_payload_failed"
;
case
sec
:
:
redundant_connection
:
return
"caf::sec::redundant_connection"
;
case
sec
:
:
remote_lookup_failed
:
return
"caf::sec::remote_lookup_failed"
;
case
sec
:
:
no_tracing_context
:
return
"caf::sec::no_tracing_context"
;
case
sec
:
:
all_requests_failed
:
return
"caf::sec::all_requests_failed"
;
case
sec
:
:
field_invariant_check_failed
:
return
"caf::sec::field_invariant_check_failed"
;
case
sec
:
:
field_value_synchronization_failed
:
return
"caf::sec::field_value_synchronization_failed"
;
case
sec
:
:
invalid_field_type
:
return
"caf::sec::invalid_field_type"
;
case
sec
:
:
unsafe_type
:
return
"caf::sec::unsafe_type"
;
case
sec
:
:
save_callback_failed
:
return
"caf::sec::save_callback_failed"
;
case
sec
:
:
load_callback_failed
:
return
"caf::sec::load_callback_failed"
;
case
sec
:
:
conversion_failed
:
return
"caf::sec::conversion_failed"
;
case
sec
:
:
connection_closed
:
return
"caf::sec::connection_closed"
;
case
sec
:
:
type_clash
:
return
"caf::sec::type_clash"
;
case
sec
:
:
unsupported_operation
:
return
"caf::sec::unsupported_operation"
;
case
sec
:
:
no_such_key
:
return
"caf::sec::no_such_key"
;
case
sec
:
:
broken_promise
:
return
"caf::sec::broken_promise"
;
case
sec
:
:
connection_timeout
:
return
"caf::sec::connection_timeout"
;
case
sec
:
:
invalid_observable
:
return
"caf::sec::invalid_observable"
;
case
sec
:
:
discarded
:
return
"caf::sec::discarded"
;
};
}
bool
from_string
(
string_view
in
,
sec
&
out
)
{
if
(
in
==
"caf::sec::none"
)
{
out
=
sec
::
none
;
return
true
;
}
else
if
(
in
==
"caf::sec::unexpected_message"
)
{
out
=
sec
::
unexpected_message
;
return
true
;
}
else
if
(
in
==
"caf::sec::unexpected_response"
)
{
out
=
sec
::
unexpected_response
;
return
true
;
}
else
if
(
in
==
"caf::sec::request_receiver_down"
)
{
out
=
sec
::
request_receiver_down
;
return
true
;
}
else
if
(
in
==
"caf::sec::request_timeout"
)
{
out
=
sec
::
request_timeout
;
return
true
;
}
else
if
(
in
==
"caf::sec::no_such_group_module"
)
{
out
=
sec
::
no_such_group_module
;
return
true
;
}
else
if
(
in
==
"caf::sec::no_actor_published_at_port"
)
{
out
=
sec
::
no_actor_published_at_port
;
return
true
;
}
else
if
(
in
==
"caf::sec::unexpected_actor_messaging_interface"
)
{
out
=
sec
::
unexpected_actor_messaging_interface
;
return
true
;
}
else
if
(
in
==
"caf::sec::state_not_serializable"
)
{
out
=
sec
::
state_not_serializable
;
return
true
;
}
else
if
(
in
==
"caf::sec::unsupported_sys_key"
)
{
out
=
sec
::
unsupported_sys_key
;
return
true
;
}
else
if
(
in
==
"caf::sec::unsupported_sys_message"
)
{
out
=
sec
::
unsupported_sys_message
;
return
true
;
}
else
if
(
in
==
"caf::sec::disconnect_during_handshake"
)
{
out
=
sec
::
disconnect_during_handshake
;
return
true
;
}
else
if
(
in
==
"caf::sec::cannot_forward_to_invalid_actor"
)
{
out
=
sec
::
cannot_forward_to_invalid_actor
;
return
true
;
}
else
if
(
in
==
"caf::sec::no_route_to_receiving_node"
)
{
out
=
sec
::
no_route_to_receiving_node
;
return
true
;
}
else
if
(
in
==
"caf::sec::failed_to_assign_scribe_from_handle"
)
{
out
=
sec
::
failed_to_assign_scribe_from_handle
;
return
true
;
}
else
if
(
in
==
"caf::sec::failed_to_assign_doorman_from_handle"
)
{
out
=
sec
::
failed_to_assign_doorman_from_handle
;
return
true
;
}
else
if
(
in
==
"caf::sec::cannot_close_invalid_port"
)
{
out
=
sec
::
cannot_close_invalid_port
;
return
true
;
}
else
if
(
in
==
"caf::sec::cannot_connect_to_node"
)
{
out
=
sec
::
cannot_connect_to_node
;
return
true
;
}
else
if
(
in
==
"caf::sec::cannot_open_port"
)
{
out
=
sec
::
cannot_open_port
;
return
true
;
}
else
if
(
in
==
"caf::sec::network_syscall_failed"
)
{
out
=
sec
::
network_syscall_failed
;
return
true
;
}
else
if
(
in
==
"caf::sec::invalid_argument"
)
{
out
=
sec
::
invalid_argument
;
return
true
;
}
else
if
(
in
==
"caf::sec::invalid_protocol_family"
)
{
out
=
sec
::
invalid_protocol_family
;
return
true
;
}
else
if
(
in
==
"caf::sec::cannot_publish_invalid_actor"
)
{
out
=
sec
::
cannot_publish_invalid_actor
;
return
true
;
}
else
if
(
in
==
"caf::sec::cannot_spawn_actor_from_arguments"
)
{
out
=
sec
::
cannot_spawn_actor_from_arguments
;
return
true
;
}
else
if
(
in
==
"caf::sec::end_of_stream"
)
{
out
=
sec
::
end_of_stream
;
return
true
;
}
else
if
(
in
==
"caf::sec::no_context"
)
{
out
=
sec
::
no_context
;
return
true
;
}
else
if
(
in
==
"caf::sec::unknown_type"
)
{
out
=
sec
::
unknown_type
;
return
true
;
}
else
if
(
in
==
"caf::sec::no_proxy_registry"
)
{
out
=
sec
::
no_proxy_registry
;
return
true
;
}
else
if
(
in
==
"caf::sec::runtime_error"
)
{
out
=
sec
::
runtime_error
;
return
true
;
}
else
if
(
in
==
"caf::sec::remote_linking_failed"
)
{
out
=
sec
::
remote_linking_failed
;
return
true
;
}
else
if
(
in
==
"caf::sec::cannot_add_upstream"
)
{
out
=
sec
::
cannot_add_upstream
;
return
true
;
}
else
if
(
in
==
"caf::sec::upstream_already_exists"
)
{
out
=
sec
::
upstream_already_exists
;
return
true
;
}
else
if
(
in
==
"caf::sec::invalid_upstream"
)
{
out
=
sec
::
invalid_upstream
;
return
true
;
}
else
if
(
in
==
"caf::sec::cannot_add_downstream"
)
{
out
=
sec
::
cannot_add_downstream
;
return
true
;
}
else
if
(
in
==
"caf::sec::downstream_already_exists"
)
{
out
=
sec
::
downstream_already_exists
;
return
true
;
}
else
if
(
in
==
"caf::sec::invalid_downstream"
)
{
out
=
sec
::
invalid_downstream
;
return
true
;
}
else
if
(
in
==
"caf::sec::no_downstream_stages_defined"
)
{
out
=
sec
::
no_downstream_stages_defined
;
return
true
;
}
else
if
(
in
==
"caf::sec::stream_init_failed"
)
{
out
=
sec
::
stream_init_failed
;
return
true
;
}
else
if
(
in
==
"caf::sec::invalid_stream_state"
)
{
out
=
sec
::
invalid_stream_state
;
return
true
;
}
else
if
(
in
==
"caf::sec::unhandled_stream_error"
)
{
out
=
sec
::
unhandled_stream_error
;
return
true
;
}
else
if
(
in
==
"caf::sec::bad_function_call"
)
{
out
=
sec
::
bad_function_call
;
return
true
;
}
else
if
(
in
==
"caf::sec::feature_disabled"
)
{
out
=
sec
::
feature_disabled
;
return
true
;
}
else
if
(
in
==
"caf::sec::cannot_open_file"
)
{
out
=
sec
::
cannot_open_file
;
return
true
;
}
else
if
(
in
==
"caf::sec::socket_invalid"
)
{
out
=
sec
::
socket_invalid
;
return
true
;
}
else
if
(
in
==
"caf::sec::socket_disconnected"
)
{
out
=
sec
::
socket_disconnected
;
return
true
;
}
else
if
(
in
==
"caf::sec::socket_operation_failed"
)
{
out
=
sec
::
socket_operation_failed
;
return
true
;
}
else
if
(
in
==
"caf::sec::unavailable_or_would_block"
)
{
out
=
sec
::
unavailable_or_would_block
;
return
true
;
}
else
if
(
in
==
"caf::sec::incompatible_versions"
)
{
out
=
sec
::
incompatible_versions
;
return
true
;
}
else
if
(
in
==
"caf::sec::incompatible_application_ids"
)
{
out
=
sec
::
incompatible_application_ids
;
return
true
;
}
else
if
(
in
==
"caf::sec::malformed_basp_message"
)
{
out
=
sec
::
malformed_basp_message
;
return
true
;
}
else
if
(
in
==
"caf::sec::serializing_basp_payload_failed"
)
{
out
=
sec
::
serializing_basp_payload_failed
;
return
true
;
}
else
if
(
in
==
"caf::sec::redundant_connection"
)
{
out
=
sec
::
redundant_connection
;
return
true
;
}
else
if
(
in
==
"caf::sec::remote_lookup_failed"
)
{
out
=
sec
::
remote_lookup_failed
;
return
true
;
}
else
if
(
in
==
"caf::sec::no_tracing_context"
)
{
out
=
sec
::
no_tracing_context
;
return
true
;
}
else
if
(
in
==
"caf::sec::all_requests_failed"
)
{
out
=
sec
::
all_requests_failed
;
return
true
;
}
else
if
(
in
==
"caf::sec::field_invariant_check_failed"
)
{
out
=
sec
::
field_invariant_check_failed
;
return
true
;
}
else
if
(
in
==
"caf::sec::field_value_synchronization_failed"
)
{
out
=
sec
::
field_value_synchronization_failed
;
return
true
;
}
else
if
(
in
==
"caf::sec::invalid_field_type"
)
{
out
=
sec
::
invalid_field_type
;
return
true
;
}
else
if
(
in
==
"caf::sec::unsafe_type"
)
{
out
=
sec
::
unsafe_type
;
return
true
;
}
else
if
(
in
==
"caf::sec::save_callback_failed"
)
{
out
=
sec
::
save_callback_failed
;
return
true
;
}
else
if
(
in
==
"caf::sec::load_callback_failed"
)
{
out
=
sec
::
load_callback_failed
;
return
true
;
}
else
if
(
in
==
"caf::sec::conversion_failed"
)
{
out
=
sec
::
conversion_failed
;
return
true
;
}
else
if
(
in
==
"caf::sec::connection_closed"
)
{
out
=
sec
::
connection_closed
;
return
true
;
}
else
if
(
in
==
"caf::sec::type_clash"
)
{
out
=
sec
::
type_clash
;
return
true
;
}
else
if
(
in
==
"caf::sec::unsupported_operation"
)
{
out
=
sec
::
unsupported_operation
;
return
true
;
}
else
if
(
in
==
"caf::sec::no_such_key"
)
{
out
=
sec
::
no_such_key
;
return
true
;
}
else
if
(
in
==
"caf::sec::broken_promise"
)
{
out
=
sec
::
broken_promise
;
return
true
;
}
else
if
(
in
==
"caf::sec::connection_timeout"
)
{
out
=
sec
::
connection_timeout
;
return
true
;
}
else
if
(
in
==
"caf::sec::invalid_observable"
)
{
out
=
sec
::
invalid_observable
;
return
true
;
}
else
if
(
in
==
"caf::sec::discarded"
)
{
out
=
sec
::
discarded
;
return
true
;
}
else
{
return
false
;
}
}
bool
from_integer
(
std
::
underlying_type_t
<
sec
>
in
,
sec
&
out
)
{
auto
result
=
static_cast
<
sec
>
(
in
);
switch
(
result
)
{
default:
return
false
;
case
sec
:
:
none
:
case
sec
:
:
unexpected_message
:
case
sec
:
:
unexpected_response
:
case
sec
:
:
request_receiver_down
:
case
sec
:
:
request_timeout
:
case
sec
:
:
no_such_group_module
:
case
sec
:
:
no_actor_published_at_port
:
case
sec
:
:
unexpected_actor_messaging_interface
:
case
sec
:
:
state_not_serializable
:
case
sec
:
:
unsupported_sys_key
:
case
sec
:
:
unsupported_sys_message
:
case
sec
:
:
disconnect_during_handshake
:
case
sec
:
:
cannot_forward_to_invalid_actor
:
case
sec
:
:
no_route_to_receiving_node
:
case
sec
:
:
failed_to_assign_scribe_from_handle
:
case
sec
:
:
failed_to_assign_doorman_from_handle
:
case
sec
:
:
cannot_close_invalid_port
:
case
sec
:
:
cannot_connect_to_node
:
case
sec
:
:
cannot_open_port
:
case
sec
:
:
network_syscall_failed
:
case
sec
:
:
invalid_argument
:
case
sec
:
:
invalid_protocol_family
:
case
sec
:
:
cannot_publish_invalid_actor
:
case
sec
:
:
cannot_spawn_actor_from_arguments
:
case
sec
:
:
end_of_stream
:
case
sec
:
:
no_context
:
case
sec
:
:
unknown_type
:
case
sec
:
:
no_proxy_registry
:
case
sec
:
:
runtime_error
:
case
sec
:
:
remote_linking_failed
:
case
sec
:
:
cannot_add_upstream
:
case
sec
:
:
upstream_already_exists
:
case
sec
:
:
invalid_upstream
:
case
sec
:
:
cannot_add_downstream
:
case
sec
:
:
downstream_already_exists
:
case
sec
:
:
invalid_downstream
:
case
sec
:
:
no_downstream_stages_defined
:
case
sec
:
:
stream_init_failed
:
case
sec
:
:
invalid_stream_state
:
case
sec
:
:
unhandled_stream_error
:
case
sec
:
:
bad_function_call
:
case
sec
:
:
feature_disabled
:
case
sec
:
:
cannot_open_file
:
case
sec
:
:
socket_invalid
:
case
sec
:
:
socket_disconnected
:
case
sec
:
:
socket_operation_failed
:
case
sec
:
:
unavailable_or_would_block
:
case
sec
:
:
incompatible_versions
:
case
sec
:
:
incompatible_application_ids
:
case
sec
:
:
malformed_basp_message
:
case
sec
:
:
serializing_basp_payload_failed
:
case
sec
:
:
redundant_connection
:
case
sec
:
:
remote_lookup_failed
:
case
sec
:
:
no_tracing_context
:
case
sec
:
:
all_requests_failed
:
case
sec
:
:
field_invariant_check_failed
:
case
sec
:
:
field_value_synchronization_failed
:
case
sec
:
:
invalid_field_type
:
case
sec
:
:
unsafe_type
:
case
sec
:
:
save_callback_failed
:
case
sec
:
:
load_callback_failed
:
case
sec
:
:
conversion_failed
:
case
sec
:
:
connection_closed
:
case
sec
:
:
type_clash
:
case
sec
:
:
unsupported_operation
:
case
sec
:
:
no_such_key
:
case
sec
:
:
broken_promise
:
case
sec
:
:
connection_timeout
:
case
sec
:
:
invalid_observable
:
case
sec
:
:
discarded
:
out
=
result
;
return
true
;
};
}
}
// namespace caf
CAF_POP_WARNINGS
libcaf_core/test/async/blocking_for_each.cpp
0 → 100644
View file @
4c68deb8
// 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 async.blocking_for_each
#include "caf/async/publisher.hpp"
#include "core-test.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/observable.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/observer.hpp"
#include "caf/scheduled_actor/flow.hpp"
using
namespace
caf
;
namespace
{
struct
fixture
{
actor_system_config
cfg
;
actor_system
sys
;
fixture
()
:
sys
(
cfg
.
set
(
"caf.scheduler.max-threads"
,
2
))
{
// nop
}
};
using
ctx_impl
=
event_based_actor
;
}
// namespace
BEGIN_FIXTURE_SCOPE
(
fixture
)
SCENARIO
(
"blocking_for_each iterates all values in a stream"
)
{
GIVEN
(
"an asynchronous source"
)
{
WHEN
(
"subscribing to its output via blocking_for_each"
)
{
THEN
(
"the observer blocks until it has received all values"
)
{
auto
inputs
=
std
::
vector
<
int
>
(
2539
,
42
);
auto
outputs
=
std
::
vector
<
int
>
{};
async
::
publisher_from
<
ctx_impl
>
(
sys
,
[](
auto
*
self
)
{
return
self
->
make_observable
().
repeat
(
42
).
take
(
2539
);
}).
blocking_for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
});
CHECK_EQ
(
inputs
,
outputs
);
}
}
}
}
END_FIXTURE_SCOPE
()
libcaf_core/test/async/bounded_buffer.cpp
0 → 100644
View file @
4c68deb8
// 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 async.bounded_buffer
#include "caf/async/bounded_buffer.hpp"
#include "core-test.hpp"
#include <memory>
#include "caf/flow/coordinator.hpp"
#include "caf/flow/merge.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/observer.hpp"
#include "caf/scheduled_actor/flow.hpp"
using
namespace
caf
;
BEGIN_FIXTURE_SCOPE
(
test_coordinator_fixture
<>
)
SCENARIO
(
"bounded buffers moves data between actors"
)
{
GIVEN
(
"a bounded buffer resource"
)
{
WHEN
(
"opening the resource from two actors"
)
{
THEN
(
"data travels through the bounded buffer"
)
{
using
actor_t
=
event_based_actor
;
auto
[
rd
,
wr
]
=
async
::
make_bounded_buffer_resource
<
int
>
(
6
,
2
);
auto
inputs
=
std
::
vector
<
int
>
{
1
,
2
,
4
,
8
,
16
,
32
,
64
,
128
};
auto
outputs
=
std
::
vector
<
int
>
{};
sys
.
spawn
([
wr
{
wr
},
&
inputs
](
actor_t
*
src
)
{
src
->
make_observable
()
.
from_container
(
inputs
)
.
filter
([](
int
)
{
return
true
;
})
.
subscribe
(
wr
);
});
sys
.
spawn
([
rd
{
rd
},
&
outputs
](
actor_t
*
snk
)
{
snk
->
make_observable
()
//
.
from_resource
(
rd
)
.
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
});
});
run
();
CHECK_EQ
(
inputs
,
outputs
);
}
}
}
}
END_FIXTURE_SCOPE
()
libcaf_core/test/async/publishing_queue.cpp
0 → 100644
View file @
4c68deb8
// 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 async.publishing_queue
#include "caf/async/publishing_queue.hpp"
#include "core-test.hpp"
#include "caf/scheduled_actor/flow.hpp"
using
namespace
caf
;
namespace
{
struct
fixture
{
actor_system_config
cfg
;
actor_system
sys
;
fixture
()
:
sys
(
cfg
.
set
(
"caf.scheduler.max-threads"
,
2
))
{
// nop
}
};
}
// namespace
BEGIN_FIXTURE_SCOPE
(
fixture
)
SCENARIO
(
"publishing queues connect asynchronous producers to observers"
)
{
GIVEN
(
"a producer and a consumer, living in separate threads"
)
{
WHEN
(
"connecting producer and consumer via a publishing queue"
)
{
THEN
(
"the consumer receives all produced values in order"
)
{
auto
[
queue
,
src
]
=
async
::
make_publishing_queue
<
int
>
(
sys
,
100
);
auto
producer_thread
=
std
::
thread
{[
q
{
std
::
move
(
queue
)}]
{
for
(
int
i
=
0
;
i
<
5000
;
++
i
)
q
->
push
(
i
);
}};
std
::
vector
<
int
>
values
;
auto
consumer_thread
=
std
::
thread
{[
&
values
,
src
{
src
}]
{
src
.
blocking_for_each
([
&
](
int
x
)
{
values
.
emplace_back
(
x
);
});
}};
producer_thread
.
join
();
consumer_thread
.
join
();
std
::
vector
<
int
>
expected_values
;
expected_values
.
resize
(
5000
);
std
::
iota
(
expected_values
.
begin
(),
expected_values
.
end
(),
0
);
CHECK_EQ
(
values
,
expected_values
);
}
}
}
}
END_FIXTURE_SCOPE
()
libcaf_core/test/detail/group_tunnel.cpp
View file @
4c68deb8
...
...
@@ -164,8 +164,7 @@ SCENARIO("tunnels dispatch published messages") {
}
WHEN
(
"an actors sends to the tunnel"
)
{
self
->
send
(
proxy
,
put_atom_v
,
42
);
THEN
(
"the message travels to the origin"
)
AND
(
"tunnel subscribers get the forwarded message eventually"
)
{
THEN
(
"the message travels to the origin and eventually to subscribers"
)
{
expect
((
sys_atom
,
forward_atom
,
message
),
from
(
self
).
to
(
worker
));
expect
((
forward_atom
,
message
),
from
(
self
).
to
(
intermediary
));
expect
((
put_atom
,
int
),
from
(
self
).
to
(
t1
).
with
(
_
,
42
));
...
...
libcaf_core/test/flow/concat.cpp
0 → 100644
View file @
4c68deb8
// 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.concat
#include "caf/flow/concat.hpp"
#include "core-test.hpp"
#include "caf/flow/observable_builder.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
();
};
}
// namespace
BEGIN_FIXTURE_SCOPE
(
fixture
)
SCENARIO
(
"concat operators combine inputs"
)
{
GIVEN
(
"two observables"
)
{
WHEN
(
"merging them to a single publisher via concat"
)
{
THEN
(
"the observer receives the output of both sources in order"
)
{
auto
outputs
=
std
::
vector
<
int
>
{};
auto
r1
=
ctx
->
make_observable
().
repeat
(
11
).
take
(
113
);
auto
r2
=
ctx
->
make_observable
().
repeat
(
22
).
take
(
223
);
flow
::
concat
(
std
::
move
(
r1
),
std
::
move
(
r2
)).
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
});
ctx
->
run
();
if
(
CHECK_EQ
(
outputs
.
size
(),
336u
))
{
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
;
}));
}
}
}
}
}
END_FIXTURE_SCOPE
()
libcaf_core/test/flow/concat_map.cpp
0 → 100644
View file @
4c68deb8
// 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.concat_map
#include "caf/flow/observable.hpp"
#include "core-test.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/merge.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/scoped_coordinator.hpp"
#include "caf/scheduled_actor/flow.hpp"
using
namespace
caf
;
namespace
{
struct
adder_state
{
static
inline
const
char
*
name
=
"adder"
;
explicit
adder_state
(
int32_t
x
)
:
x
(
x
)
{
// nop
}
caf
::
behavior
make_behavior
()
{
return
{
[
this
](
int32_t
y
)
{
return
x
+
y
;
},
};
}
int32_t
x
;
};
using
adder_actor
=
stateful_actor
<
adder_state
>
;
struct
fixture
:
test_coordinator_fixture
<>
{
flow
::
scoped_coordinator_ptr
ctx
=
flow
::
make_scoped_coordinator
();
};
}
// namespace
BEGIN_FIXTURE_SCOPE
(
fixture
)
SCENARIO
(
"concat_map merges multiple observables"
)
{
using
i32_list
=
std
::
vector
<
int32_t
>
;
GIVEN
(
"a generation that emits lists"
)
{
WHEN
(
"lifting each list to an observable with concat_map"
)
{
THEN
(
"the observer receives values from all observables one by one"
)
{
auto
outputs
=
i32_list
{};
auto
inputs
=
std
::
vector
<
i32_list
>
{
i32_list
{
1
},
i32_list
{
2
,
2
},
i32_list
{
3
,
3
,
3
},
};
ctx
->
make_observable
()
.
from_container
(
inputs
)
.
concat_map
([
this
](
const
i32_list
&
x
)
{
return
ctx
->
make_observable
().
from_container
(
x
);
})
.
for_each
([
&
outputs
](
int32_t
x
)
{
outputs
.
emplace_back
(
x
);
});
ctx
->
run
();
auto
expected_outputs
=
i32_list
{
1
,
2
,
2
,
3
,
3
,
3
};
CHECK_EQ
(
outputs
,
expected_outputs
);
}
}
}
GIVEN
(
"a generation that emits 10 integers"
)
{
WHEN
(
"sending a request for each each integer"
)
{
THEN
(
"concat_map merges the responses one by one"
)
{
auto
outputs
=
i32_list
{};
auto
adder
=
sys
.
spawn
<
adder_actor
>
(
1
);
auto
[
self
,
launch
]
=
sys
.
spawn_inactive
<
event_based_actor
>
();
auto
inputs
=
i32_list
(
10
);
std
::
iota
(
inputs
.
begin
(),
inputs
.
end
(),
0
);
self
->
make_observable
()
.
from_container
(
inputs
)
.
concat_map
([
self
{
self
},
add1
{
adder
}](
int32_t
x
)
{
return
self
->
request
(
add1
,
infinite
,
x
).
as_observable
<
int32_t
>
();
})
.
for_each
([
&
outputs
](
int32_t
x
)
{
outputs
.
emplace_back
(
x
);
});
launch
();
run
();
auto
expected_outputs
=
i32_list
{
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
};
CHECK_EQ
(
outputs
,
expected_outputs
);
}
}
}
}
END_FIXTURE_SCOPE
()
libcaf_core/test/flow/flat_map.cpp
0 → 100644
View file @
4c68deb8
// 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.flat_map
#include "caf/flow/observable.hpp"
#include "core-test.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/merge.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/scoped_coordinator.hpp"
#include "caf/scheduled_actor/flow.hpp"
using
namespace
caf
;
namespace
{
struct
adder_state
{
static
inline
const
char
*
name
=
"adder"
;
explicit
adder_state
(
int32_t
x
)
:
x
(
x
)
{
// nop
}
caf
::
behavior
make_behavior
()
{
return
{
[
this
](
int32_t
y
)
{
return
x
+
y
;
},
};
}
int32_t
x
;
};
using
adder_actor
=
stateful_actor
<
adder_state
>
;
struct
fixture
:
test_coordinator_fixture
<>
{
flow
::
scoped_coordinator_ptr
ctx
=
flow
::
make_scoped_coordinator
();
};
}
// namespace
BEGIN_FIXTURE_SCOPE
(
fixture
)
SCENARIO
(
"flat_map merges multiple observables"
)
{
using
i32_list
=
std
::
vector
<
int32_t
>
;
GIVEN
(
"a generation that emits lists"
)
{
WHEN
(
"lifting each list to an observable with flat_map"
)
{
THEN
(
"the observer receives values from all observables"
)
{
auto
outputs
=
i32_list
{};
auto
inputs
=
std
::
vector
<
i32_list
>
{
i32_list
{
1
},
i32_list
{
2
,
2
},
i32_list
{
3
,
3
,
3
},
};
ctx
->
make_observable
()
.
from_container
(
inputs
)
.
flat_map
([
this
](
const
i32_list
&
x
)
{
return
ctx
->
make_observable
().
from_container
(
x
);
})
.
for_each
([
&
outputs
](
int32_t
x
)
{
outputs
.
emplace_back
(
x
);
});
ctx
->
run
();
std
::
sort
(
outputs
.
begin
(),
outputs
.
end
());
auto
expected_outputs
=
i32_list
{
1
,
2
,
2
,
3
,
3
,
3
};
CHECK_EQ
(
outputs
,
expected_outputs
);
}
}
}
GIVEN
(
"a generation that emits 10 integers"
)
{
WHEN
(
"sending a request for each each integer"
)
{
THEN
(
"flat_map merges the responses"
)
{
auto
outputs
=
i32_list
{};
auto
inputs
=
i32_list
(
10
);
std
::
iota
(
inputs
.
begin
(),
inputs
.
end
(),
0
);
auto
adder
=
sys
.
spawn
<
adder_actor
>
(
1
);
auto
[
self
,
launch
]
=
sys
.
spawn_inactive
<
event_based_actor
>
();
self
->
make_observable
()
.
from_container
(
inputs
)
.
flat_map
([
self
{
self
},
adder
](
int32_t
x
)
{
return
self
->
request
(
adder
,
infinite
,
x
).
as_observable
<
int32_t
>
();
})
.
for_each
([
&
outputs
](
int32_t
x
)
{
outputs
.
emplace_back
(
x
);
});
launch
();
run
();
std
::
sort
(
outputs
.
begin
(),
outputs
.
end
());
auto
expected_outputs
=
i32_list
{
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
};
CHECK_EQ
(
outputs
,
expected_outputs
);
}
}
}
}
END_FIXTURE_SCOPE
()
libcaf_core/test/flow/for_each.cpp
0 → 100644
View file @
4c68deb8
// 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.for_each
#include "caf/flow/observable.hpp"
#include "core-test.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/merge.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/observer.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
();
};
}
// namespace
BEGIN_FIXTURE_SCOPE
(
fixture
)
SCENARIO
(
"for_each iterates all values in a stream"
)
{
GIVEN
(
"a generation"
)
{
WHEN
(
"subscribing to its output via for_each"
)
{
THEN
(
"the observer receives all values"
)
{
/* subtest */
{
auto
inputs
=
std
::
vector
<
int
>
{
1
,
2
,
4
,
8
,
16
,
32
,
64
,
128
};
auto
outputs
=
std
::
vector
<
int
>
{};
ctx
->
make_observable
()
.
from_container
(
inputs
)
//
.
filter
([](
int
)
{
return
true
;
})
.
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
});
ctx
->
run
();
CHECK_EQ
(
inputs
,
outputs
);
}
/* subtest */
{
auto
inputs
=
std
::
vector
<
int
>
{
21
,
21
,
21
,
21
,
21
,
21
,
21
};
auto
outputs
=
std
::
vector
<
int
>
{};
ctx
->
make_observable
()
.
repeat
(
7
)
//
.
take
(
7
)
.
map
([](
int
x
)
{
return
x
*
3
;
})
.
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
});
ctx
->
run
();
CHECK_EQ
(
inputs
,
outputs
);
}
}
}
}
GIVEN
(
"a transformation"
)
{
WHEN
(
"subscribing to its output via for_each"
)
{
THEN
(
"the observer receives all values"
)
{
/* subtest */
{
auto
inputs
=
std
::
vector
<
int
>
{
1
,
2
,
4
,
8
,
16
,
32
,
64
,
128
};
auto
outputs
=
std
::
vector
<
int
>
{};
ctx
->
make_observable
()
.
from_container
(
inputs
)
//
.
as_observable
()
.
filter
([](
int
)
{
return
true
;
})
.
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
});
ctx
->
run
();
CHECK_EQ
(
inputs
,
outputs
);
}
/* subtest */
{
auto
inputs
=
std
::
vector
<
int
>
{
21
,
21
,
21
,
21
,
21
,
21
,
21
};
auto
outputs
=
std
::
vector
<
int
>
{};
ctx
->
make_observable
()
.
repeat
(
7
)
//
.
as_observable
()
.
take
(
7
)
.
map
([](
int
x
)
{
return
x
*
3
;
})
.
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
});
ctx
->
run
();
CHECK_EQ
(
inputs
,
outputs
);
}
}
}
}
GIVEN
(
"an observable"
)
{
WHEN
(
"subscribing to its output via for_each"
)
{
THEN
(
"the observer receives all values"
)
{
/* subtest */
{
auto
inputs
=
std
::
vector
<
int
>
{
1
,
2
,
4
,
8
,
16
,
32
,
64
,
128
};
auto
outputs
=
std
::
vector
<
int
>
{};
ctx
->
make_observable
()
.
from_container
(
inputs
)
//
.
filter
([](
int
)
{
return
true
;
})
.
as_observable
()
.
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
});
ctx
->
run
();
CHECK_EQ
(
inputs
,
outputs
);
}
/* subtest */
{
auto
inputs
=
std
::
vector
<
int
>
{
21
,
21
,
21
,
21
,
21
,
21
,
21
};
auto
outputs
=
std
::
vector
<
int
>
{};
ctx
->
make_observable
()
.
repeat
(
7
)
//
.
take
(
7
)
.
map
([](
int
x
)
{
return
x
*
3
;
})
.
as_observable
()
.
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
});
ctx
->
run
();
CHECK_EQ
(
inputs
,
outputs
);
}
}
}
}
}
END_FIXTURE_SCOPE
()
libcaf_core/test/flow/merge.cpp
0 → 100644
View file @
4c68deb8
// 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.merge
#include "caf/flow/merge.hpp"
#include "core-test.hpp"
#include "caf/flow/observable_builder.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
();
};
}
// namespace
BEGIN_FIXTURE_SCOPE
(
fixture
)
SCENARIO
(
"merge operators combine inputs"
)
{
GIVEN
(
"two observables"
)
{
WHEN
(
"merging them to a single publisher"
)
{
THEN
(
"the observer receives the output of both sources"
)
{
auto
outputs
=
std
::
vector
<
int
>
{};
auto
r1
=
ctx
->
make_observable
().
repeat
(
11
).
take
(
113
);
auto
r2
=
ctx
->
make_observable
().
repeat
(
22
).
take
(
223
);
flow
::
merge
(
std
::
move
(
r1
),
std
::
move
(
r2
)).
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
});
ctx
->
run
();
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
;
}));
}
}
}
}
}
END_FIXTURE_SCOPE
()
libcaf_core/test/flow/observe_on.cpp
0 → 100644
View file @
4c68deb8
// 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.observe_on
#include "caf/flow/observable.hpp"
#include "core-test.hpp"
#include <memory>
#include "caf/flow/coordinator.hpp"
#include "caf/flow/merge.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/observer.hpp"
#include "caf/scheduled_actor/flow.hpp"
using
namespace
caf
;
BEGIN_FIXTURE_SCOPE
(
test_coordinator_fixture
<>
)
SCENARIO
(
"observe_on moves data between actors"
)
{
GIVEN
(
"a generation"
)
{
WHEN
(
"calling observe_on"
)
{
THEN
(
"the target actor observes all values"
)
{
using
actor_t
=
event_based_actor
;
auto
inputs
=
std
::
vector
<
int
>
{
1
,
2
,
4
,
8
,
16
,
32
,
64
,
128
};
auto
outputs
=
std
::
vector
<
int
>
{};
auto
[
src
,
launch_src
]
=
sys
.
spawn_inactive
<
actor_t
>
();
auto
[
snk
,
launch_snk
]
=
sys
.
spawn_inactive
<
actor_t
>
();
src
->
make_observable
()
.
from_container
(
inputs
)
.
filter
([](
int
)
{
return
true
;
})
.
observe_on
(
snk
)
.
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
});
launch_src
();
launch_snk
();
run
();
CHECK_EQ
(
inputs
,
outputs
);
}
}
}
}
END_FIXTURE_SCOPE
()
libcaf_core/test/flow/single.cpp
0 → 100644
View file @
4c68deb8
// 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.single
#include "caf/flow/single.hpp"
#include "core-test.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
();
};
}
// namespace
CAF_TEST_FIXTURE_SCOPE
(
single_tests
,
fixture
)
SCENARIO
(
"singles emit at most one value"
)
{
using
i32_list
=
std
::
vector
<
int32_t
>
;
GIVEN
(
"a single<int32>"
)
{
WHEN
(
"an observer subscribes before the values has been set"
)
{
THEN
(
"the observer receives the value when calling set_value"
)
{
auto
outputs
=
i32_list
{};
auto
single_int
=
flow
::
make_single
<
int32_t
>
(
ctx
.
get
());
single_int
//
.
as_observable
()
.
for_each
([
&
outputs
](
int32_t
x
)
{
outputs
.
emplace_back
(
x
);
});
ctx
->
run
();
CHECK_EQ
(
outputs
,
i32_list
());
single_int
.
set_value
(
42
);
CHECK_EQ
(
outputs
,
i32_list
({
42
}));
}
}
WHEN
(
"an observer subscribes after the values has been set"
)
{
THEN
(
"the observer receives the value immediately"
)
{
auto
outputs
=
i32_list
{};
auto
single_int
=
flow
::
make_single
<
int32_t
>
(
ctx
.
get
());
single_int
.
set_value
(
42
);
single_int
//
.
as_observable
()
.
for_each
([
&
outputs
](
int32_t
x
)
{
outputs
.
emplace_back
(
x
);
});
ctx
->
run
();
CHECK_EQ
(
outputs
,
i32_list
({
42
}));
}
}
}
}
CAF_TEST_FIXTURE_SCOPE_END
()
libcaf_core/test/response_handle.cpp
0 → 100644
View file @
4c68deb8
// 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 response_handle
#include "caf/response_handle.hpp"
#include "core-test.hpp"
#include "caf/scheduled_actor/flow.hpp"
using
namespace
caf
;
namespace
{
struct
dummy_state
{
static
inline
const
char
*
name
=
"dummy"
;
caf
::
behavior
make_behavior
()
{
return
{
[](
int32_t
x
)
->
result
<
int32_t
>
{
if
(
x
%
2
==
0
)
return
x
+
x
;
else
return
make_error
(
sec
::
invalid_argument
);
},
};
}
};
using
dummy_actor
=
stateful_actor
<
dummy_state
>
;
struct
fixture
:
test_coordinator_fixture
<>
{
actor
dummy
;
actor
aut
;
fixture
()
{
dummy
=
sys
.
spawn
<
dummy_actor
>
();
sched
.
run
();
}
};
}
// namespace
BEGIN_FIXTURE_SCOPE
(
fixture
)
SCENARIO
(
"response handles are convertible to observables and singles"
)
{
GIVEN
(
"a response handle that produces a valid result"
)
{
WHEN
(
"calling as_single"
)
{
THEN
(
"observers see the result"
)
{
using
result_t
=
std
::
variant
<
none_t
,
int32_t
,
caf
::
error
>
;
result_t
result
;
auto
[
self
,
launch
]
=
sys
.
spawn_inactive
<
event_based_actor
>
();
self
->
request
(
dummy
,
infinite
,
int32_t
{
42
})
.
as_single
<
int32_t
>
()
.
subscribe
([
&
result
](
int32_t
val
)
{
result
=
val
;
},
[
&
result
](
const
error
&
what
)
{
result
=
what
;
});
launch
();
expect
((
int32_t
),
from
(
aut
).
to
(
dummy
).
with
(
42
));
expect
((
int32_t
),
from
(
dummy
).
to
(
aut
).
with
(
84
));
CHECK
(
!
sched
.
has_job
());
CHECK_EQ
(
result
,
result_t
{
84
});
}
}
WHEN
(
"calling as_observable"
)
{
THEN
(
"observers see the result"
)
{
using
result_t
=
std
::
variant
<
none_t
,
int32_t
,
caf
::
error
>
;
size_t
on_next_calls
=
0
;
bool
completed
=
false
;
result_t
result
;
auto
[
self
,
launch
]
=
sys
.
spawn_inactive
<
event_based_actor
>
();
self
->
request
(
dummy
,
infinite
,
int32_t
{
42
})
.
as_observable
<
int32_t
>
()
.
for_each
(
[
&
](
int32_t
val
)
{
result
=
val
;
++
on_next_calls
;
},
[
&
](
const
error
&
what
)
{
result
=
what
;
},
[
&
]
{
completed
=
true
;
});
launch
();
expect
((
int32_t
),
from
(
aut
).
to
(
dummy
).
with
(
42
));
expect
((
int32_t
),
from
(
dummy
).
to
(
aut
).
with
(
84
));
CHECK
(
!
sched
.
has_job
());
CHECK_EQ
(
result
,
result_t
{
84
});
CHECK_EQ
(
on_next_calls
,
1u
);
CHECK
(
completed
);
}
}
}
GIVEN
(
"a response handle that produces an error"
)
{
WHEN
(
"calling as_single"
)
{
THEN
(
"observers see an error"
)
{
using
result_t
=
std
::
variant
<
none_t
,
int32_t
,
caf
::
error
>
;
result_t
result
;
auto
[
self
,
launch
]
=
sys
.
spawn_inactive
<
event_based_actor
>
();
self
->
request
(
dummy
,
infinite
,
int32_t
{
13
})
.
as_single
<
int32_t
>
()
.
subscribe
([
&
result
](
int32_t
val
)
{
result
=
val
;
},
[
&
result
](
const
error
&
what
)
{
result
=
what
;
});
launch
();
expect
((
int32_t
),
from
(
aut
).
to
(
dummy
).
with
(
13
));
expect
((
error
),
from
(
dummy
).
to
(
aut
));
CHECK
(
!
sched
.
has_job
());
CHECK_EQ
(
result
,
result_t
{
make_error
(
sec
::
invalid_argument
)});
}
}
WHEN
(
"calling as_observable"
)
{
THEN
(
"observers see an error"
)
{
using
result_t
=
std
::
variant
<
none_t
,
int32_t
,
caf
::
error
>
;
size_t
on_next_calls
=
0
;
bool
completed
=
false
;
result_t
result
;
auto
[
self
,
launch
]
=
sys
.
spawn_inactive
<
event_based_actor
>
();
self
->
request
(
dummy
,
infinite
,
int32_t
{
13
})
.
as_observable
<
int32_t
>
()
.
for_each
(
[
&
](
int32_t
val
)
{
result
=
val
;
++
on_next_calls
;
},
[
&
](
const
error
&
what
)
{
result
=
what
;
},
[
&
]
{
completed
=
true
;
});
launch
();
expect
((
int32_t
),
from
(
aut
).
to
(
dummy
).
with
(
13
));
expect
((
error
),
from
(
dummy
).
to
(
aut
));
CHECK
(
!
sched
.
has_job
());
CHECK_EQ
(
result
,
result_t
{
make_error
(
sec
::
invalid_argument
)});
CHECK_EQ
(
on_next_calls
,
0u
);
CHECK
(
!
completed
);
}
}
}
}
END_FIXTURE_SCOPE
()
libcaf_test/caf/test/bdd_dsl.hpp
View file @
4c68deb8
...
...
@@ -20,10 +20,18 @@
CAF_MESSAGE("WHEN " description); \
if (true)
#define AND_WHEN(description) \
CAF_MESSAGE("AND WHEN " description); \
if (true)
#define THEN(description) \
CAF_MESSAGE("THEN " description); \
if (true)
#define AND_THEN(description) \
CAF_MESSAGE("AND THEN " description); \
if (true)
#define AND(description) \
{} \
CAF_MESSAGE("AND " description); \
...
...
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