Pipes
Pipes can be used to compose functionality, such that data streams through a pipe and is modified on the go. Pipes are sequence of objects, which inspect and/or modify the data that is passed through it.
A pipe consists of pipe segments, which are combined into a single pipe object.
Pipe segments cannot be accessed or split in run-time, they are absorbed into some complex template-based recursive type, implementing the Pipe
interface.
Pipes, however, can be connected dynamically.
Pipes always have the following form, for a given type T
:
1auto pipe =
2 Entry<T>{} >>
3 // pipe segments...
4 Exit{};
pipe
is a pipe instance, taking data of type T
, and producing it, such as 1 >> pipe;
, or pipe.extract();
.
It can be connected to other pipe instances dynamically.
1auto pipe =
2 Entry<T>{} >>
3 // pipe segments...
4 Cap{};
In this case, pipe
is an instance, with a cap at the end; it does not allow dynamic binding to other pipes.
1auto& pipe =
2 Entry<T>{} >>
3 // pipe segments...
4 Ref{};
Now, pipe
is a reference, like the capped pipe above.
It is automatically destroyed at shutdown (by the stored::pipes::gc
Group
).
When a group reference is passed to Ref{...}
, the pipe is still allocated using the default allocator, but added to the provided group, instead of gc
.
Pipe segments can be connected, such as:
1auto& pipe =
2 Entry<int>{} >>
3 Tee{
4 Entry<int>{} >>
5 Log<int>{"tee 1"} >>
6 Ref{},
7
8 Entry<int>{} >>
9 Call{[](int x) { printf("tee 2 = %d\n", x); }} >>
10 Ref{}
11 } >>
12 Buffer<int>{} >>
13 Cap{};
A pipe segment has to adhere to the following rules:
It must be a
struct
orclass
. No specific base classes have to be used. No specific (virtual) interface is defined.It must implement a
B inject(A)
member function, where the typesA
andB
can be freely chosen. In fact, havinginject()
makes a class a pipe segment. Theinject()
function is used to extract the pipe segment’s input typeA
and output typeB
. You are free to choose if the type is a value or reference, or cv-qualified.It may implement
B_ extract()
, whereB_
must compatible withB
. However,B
could beconst&
, whileB_
is just a value, for example.It may implement
A_ entry_cast(B_) const
and/orB_ exit_cast(A_) const
, whereA_
andB_
for both functions must be compatible withA
andB
.It may implement
B_ trigger(bool*)
, whereB_
must be compatible withB
.It must be move-constructable.
Preferably, keep pipe segments simple and small. Create complexity by composition.
For examples, check include/libstored/pipes.h
, and especially the Identity
class implementation for the simplest form.
The following functions are available for pipes, and can be available for pipe segments:
B inject(A)
Inject a value into the pipe (segment). The function returns the output of the pipe (segment). This is the normal direction of the data flow.
B extract()
This function tries to find data from the exit of the pipe back through the segments. Usually, it returns the value of the last
Buffer
in the pipe. If there is no such segment, a default constructedB
is returned. If a pipe segment does not support extraction, it can omit the function.A entry_cast(B) const
Type-cast the given pipe (segment) output type to the input type. It should not modify the state of the pipe (segment). When omitted, it is assumed that the input and output types are assignable.
B exit_cast(A) const
Type-cast the given pipe (segment) input type to the output type. It should not modify the state of the pipe (segment). When omitted, it is assumed that the input and output types are assignable.
B trigger(bool*)
Some pipe segments have a special side-effect, such as reading external data. It may implement the trigger function to perform this side-effect. The parameter can be used to indicate if the pipe (segment) has returned any data by writing
true
to the provided pointer. The first pipe segment that provides data by a trigger, injects this data into the remainder of the pipe.
stored::pipes::PipeBase
-
class PipeBase
Common base class of all pipes.
Subclassed by stored::pipes::Pipe< std::decay_t< segment_traits< S >::type_in >, std::decay_t< segment_traits< S >::type_out > >, stored::pipes::Pipe< In, Out >
stored::pipes::Pipe
stored::pipes::PipeEntry
-
template<typename In>
class PipeEntry Virtual base class for any segment/pipe with specific in type.
Subclassed by stored::pipes::Pipe< std::decay_t< segment_traits< S >::type_in >, std::decay_t< segment_traits< S >::type_out > >, stored::pipes::Pipe< In, Out >
stored::pipes::PipeExit
-
template<typename Out>
class PipeExit Virtual base class for any segment/pipe with specific out type.
Subclassed by stored::pipes::Pipe< std::decay_t< segment_traits< S >::type_in >, std::decay_t< segment_traits< S >::type_out > >, stored::pipes::Pipe< In, Out >
stored::pipes::Group
-
class Group
A set of pipes.
There is one special group: stored::pipes::gc
, which destroys pipes created with default Ref
.
stored::pipes::Buffer
-
template<typename T>
class Buffer Memory element for injected values.
The constructor accepts an optional argument as initial value stored in the buffer. Extracted data is returned from this memory.
Subclassed by stored::pipes::Triggered< T, Compare, N >
stored::pipes::Call
-
template<typename T, typename F = void(T)>
class Call Invoke a function for every inject.
The function prototype can be:
void(T)
void(T const&)
void(T&)
T(T)
The first two cases allow a function to observe the injected value. The last two cases allow modifying it; the modified/returned value is passed downstream.
T
andF
are auto-deducted in C++17.
stored::pipes::Cast
stored::pipes::Changes
-
template<typename T, typename Compare = std::equal_to<T>>
class Changes Forward injected values, when they are different.
By default the comparator uses == for comparison. Override with a custom type, such as stored::pipes::similar_to.
stored::pipes::Constrained
-
template<typename Constraints>
class Constrained Applies constraints to the value in the pipe.
Injected data is passed to the
operator()
of theConstraints
instance, and its result is passed further on through the pipe. TheConstraints::operator()
is assumed to be stateless.See also
Public Types
-
using type = typename impl::constraints_type<decltype(&Constraints::operator())>::type
Public Functions
-
template<typename Constraints_ = Constraints, std::enable_if_t<std::is_default_constructible<Constraints_>::value, int> = 0>
inline Constrained()
-
template<typename Constraints_, std::enable_if_t<std::is_constructible<Constraints, Constraints_&&>::value, int> = 0>
inline explicit Constrained(Constraints_ &&constraints)
-
using type = typename impl::constraints_type<decltype(&Constraints::operator())>::type
-
template<typename T>
class Bounded Constraints that bounds the value of the pipe between a min and max bound.
See also
stored::pipes::Convert
-
template<typename Converter>
class Convert Converts a value in the pipe, given a converter class.
Injected data is passed to the
exit_cast()
of theConverter
instance, and its result is passed further on through the pipe. For extract, theentry_cast()
of theConverter
instance is used. Both methods of the Converter are assumed to be stateless.See also
Public Functions
-
template<typename Converter_ = Converter, std::enable_if_t<std::is_default_constructible<Converter_>::value, int> = 0>
inline Convert()
-
template<typename Converter_, std::enable_if_t<std::is_constructible<Converter, Converter_&&>::value, int> = 0>
inline explicit Convert(Converter_ &&converter)
-
template<typename Converter_ = Converter, std::enable_if_t<std::is_default_constructible<Converter_>::value, int> = 0>
-
template<typename T, typename ratio = std::ratio<1, 1>>
class Scale Converts a values by means of a scale factor.
See also
- Template Parameters:
T – the type of the data, which must be a floating point type
ratio –
std::ratio
or equivalent
stored::pipes::Get
-
template<typename T, typename V>
class Get Extract a store object’s value upon every extract/inject.
The value passed through the pipe entry is ignored. Upon
trigger()
, the value from the object is passed through the pipe.V can be a stored::Variant, and any reference to a fixed-type store object. Pass it to the constructor.
Usually, use C++17 auto-deduction to determine both
T
andV
.
stored::pipes::Identity
-
template<typename T>
class Identity Pipe segment without side effects.
Subclassed by stored::pipes::impl::Cast< T, T, is_number >
stored::pipes::Log
-
template<typename T>
class Log Invokes a logger function for injected values.
By default, the value is printed to
stdout
. If a custom logger function is provided, it must accept a name (std::string const&
) and value (T
).
stored::pipes::Map
-
template<typename T, size_t N, typename CompareValue = std::equal_to<T>>
class IndexMap An index to T map.
It addresses an array to find the corresponding value. All indices [0,N[ should be present.
find() complexity is O(1), rfind() is O(N).
Public Functions
-
inline explicit constexpr IndexMap(std::array<value_type, N> &&map)
-
inline explicit constexpr IndexMap(std::array<value_type, N> const &map)
-
template<typename T0_, typename ...T_, std::enable_if_t<sizeof...(T_) + 1 == N && std::is_constructible<value_type, T0_>::value, int> = 0>
inline explicit constexpr IndexMap(T0_ &&t0, T_&&... t)
-
template<typename Key>
inline constexpr value_type const &find(Key &&index) const
-
inline constexpr size_t rfind(value_type const &value) const
-
inline explicit constexpr IndexMap(std::array<value_type, N> &&map)
-
template<typename Key, typename Value, size_t N, typename CompareKey = std::less<Key>, typename CompareValue = std::equal_to<Value>>
class OrderedMap An Key to Value map with ordered keys.
Internally, all key-value pairs are saved in a array. To perform a binary search through this array, the keys must have a natural order (usually by implementing
operator<
), and the values must be sorted for initialization.find() complexity is O(log(N)), rfind() is O(N).
Public Types
-
using element_type = std::pair<key_type, value_type>
Public Functions
-
inline explicit constexpr OrderedMap(std::array<element_type, N> &&map)
-
inline explicit constexpr OrderedMap(std::array<element_type, N> const &map)
-
template<typename T0_, typename ...T_, std::enable_if_t<sizeof...(T_) + 1 == N && std::is_constructible<element_type, T0_>::value, int> = 0>
inline explicit constexpr OrderedMap(T0_ &&t0, T_&&... t)
-
inline value_type const &find(key_type const &key) const
-
inline constexpr key_type const &rfind(value_type const &value) const
-
using element_type = std::pair<key_type, value_type>
-
template<typename Key, typename Value, size_t N, typename CompareKey = std::equal_to<Key>, typename CompareValue = std::equal_to<Value>>
class RandomMap A Key to value Map for arbitrary types.
In contrast to the OrderedMap, the keys do not have to be ordered, or even have some natural order. Keys are compared using == (or equivalent). This makes searching linear, but allows different types.
find() and rfind() complexity is both O(N).
Public Types
Public Functions
-
inline explicit constexpr RandomMap(std::array<element_type, N> &&map)
-
inline explicit constexpr RandomMap(std::array<element_type, N> const &map)
-
template<typename T0_, typename ...T_, std::enable_if_t<sizeof...(T_) + 1 == N && std::is_constructible<element_type, T0_>::value, int> = 0>
inline explicit constexpr RandomMap(T0_ &&t0, T_&&... t)
-
inline constexpr value_type const &find(key_type const &key) const
-
inline constexpr key_type const &rfind(value_type const &value) const
-
inline explicit constexpr RandomMap(std::array<element_type, N> &&map)
-
template<typename Key, typename Value, size_t N, typename CompareKey = std::equal_to<Key>, typename CompareValue = std::equal_to<Value>>
auto stored::pipes::make_random_map(std::pair<Key, Value> const (&kv)[N], CompareKey compareKey = CompareKey{}, CompareValue compareValue = CompareValue{}) A helper function to create the appropriate RandomMap instance.
The resulting map may be passed to stored::pipes::Mapped.
-
template<typename From, typename To, typename MapType>
class Mapped An associative map.
The inject value is translated using the constructor-provided map. If the injected value is not found in the map, the first element of the map is used.
The
MapType
must have:a type
key_type
(equivalent toFrom
) for C++17 template deductiona type
value_type
(equivalent toTo
) for C++17 template deductionFrom find(To)
(whereFrom
/To
may beconst&
)To rfind(From)
(whereFrom
/To
may beconst&
)
-
template<typename Key, typename Value, size_t N, typename CompareKey = std::less<Key>, typename CompareValue = std::equal_to<Value>>
auto stored::pipes::Map(std::pair<Key, Value> const (&kv)[N], CompareKey compareKey = CompareKey{}, CompareValue compareValue = CompareValue{}) An associative map.
Actually, this is a helper function to create a stored::pipes::Mapped with the appropriate stored::pipes::OrderedMap.
Example:
Map({{1, 10}, {2, 20}, {3, 30}})
-
template<typename T, size_t N, typename CompareValue = std::equal_to<T>>
auto stored::pipes::Map(T const (&values)[N], CompareValue compareValue = CompareValue{}) An associative map.
Actually, this is a helper function to create a stored::pipes::Mapped with the appropriate stored::pipes::IndexMap.
Example:
Map({1, 2, 3})
-
template<typename T0, typename T1, typename ...T>
constexpr auto stored::pipes::Map(T0 &&v0, T1 &&v1, T&&... v) An associative map.
Actually, this is a helper function to create a stored::pipes::Mapped with the appropriate stored::pipes::IndexMap.
Example:
Map(1, 2, 3)
stored::pipes::Mux
-
template<typename T, size_t N>
class Mux Multiplex pipes, given the injected index value.
Pipes passed to the constructor are saved as references. Given the injected value, the corresponding pipe is extracted when required.
stored::pipes::RateLimit
-
template<typename T, typename Compare = std::equal_to<T>, typename Duration = std::chrono::milliseconds, typename Clock = std::chrono::steady_clock>
class RateLimit Forward injected values, when they are different, with a minimum interval.
By default the comparator uses == for comparison. Override with a custom type, such as stored::pipes::similar_to.
Public Functions
-
inline explicit RateLimit(PipeEntry<type> &p, duration_type interval)
-
template<typename T_>
inline RateLimit(PipeEntry<type> &p, duration_type interval, T_ &&init)
-
inline type const &trigger(bool *triggered = nullptr, typename clock_type::time_point now = clock_type::now())
-
inline explicit RateLimit(PipeEntry<type> &p, duration_type interval)
stored::pipes::Set
stored::pipes::Signal
-
template<typename T, typename Key = void*, typename Token = void*>
class Signal
stored::pipes::Tee
-
template<typename T, size_t N>
class Tee Forwards injected data into a fixed list of other pipes.
Pass any positive number of references to
PipeEntry<T>
to the constructor.When using C++17, the template parameters are auto-deduced from the provided pipes.
stored::pipes::Transistor
-
template<typename T, bool invert = false, typename Gate = bool>
class Transistor Pass either the value or a default-constructed value through the pipe, depending on a gate pipe.
The gate pipe value is extracted upon every inject.
stored::pipes::Triggered
-
template<typename T, typename Compare = std::equal_to<std::decay_t<T>>, size_t N = 1>
class Triggered : private stored::pipes::Buffer<T>, private stored::pipes::Tee<T, 1> A buffer, which forwards the last injected data to other pipes when triggered.
Think of it as a write-back cache, where the write-back action is triggered using
trigger()
. To determine if the data has changed, theCompare
template parameter is used.Public Types
-
using type = typename buffer::type
-
using type_in = typename buffer::type_in
-
using type_out = typename buffer::type_out
Public Functions
-
template<typename ...P, std::enable_if_t<sizeof...(P) + 1 == N, int> = 0>
inline explicit constexpr Triggered(PipeEntry<T> &p0, P&... p)
-
template<typename type_, std::enable_if_t<std::is_constructible<type, type_>::value, int> = 0>
inline explicit constexpr Triggered(type_ &&x)
-
using type = typename buffer::type