From f82b8ed34712487a9c2859d3c88a075687033a3d Mon Sep 17 00:00:00 2001 From: Krylov Yaroslav Date: Sun, 16 Jul 2023 17:50:16 +0400 Subject: [PATCH] version 0.14.0 --- CHANGELOG.md | 46 + include/ureact/version.hpp | 2 +- single_include/ureact/ureact_amalgamated.hpp | 1527 ++++++++++-------- 3 files changed, 858 insertions(+), 717 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3613b8..c3fdc2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,52 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [0.14.0](https://github.com/YarikTH/ureact/releases/tag/0.14.0) (2023-07-16) + +[Full Changelog](https://github.com/YarikTH/ureact/compare/0.13.0...0.14.0) + +Rework header structure and rework observers + +- Add `ureact::happened` adaptor +- BREAKING! Big structure update \ + Root `ureact` folder become clearer (6 headers vs 14) and `ureact/adaptor` + folder now mostly contain only so named adaptors in each header. + - move `ureact/default_context.hpp` content into `ureact/context.hpp` + it contains a single function, and it is unconditionally included + in `ureact/signal.hpp` and `ureact/events.hpp` anyway + - move `ureact/temp_signal.hpp` into `ureact/detail` + because it is not intended to be included and used by library user + - move `ureact/has_changed.hpp` into `ureact/detail` + because it is not intended to be included and used by library user. User + can define its own `has_changed` overload, but `has_changed.hpp` is not + needed to do so. + - move some headers that are not useful by themselves in a + new `ureact/utility` directory. Namely: + - `ureact/unit.hpp` + - `ureact/type_traits.hpp` + - `ureact/signal_pack.hpp` + - `ureact/event_range.hpp` + - `ureact/event_emitter.hpp` + - extract `ureact::join_with` from `ureact/adaptor/join.hpp` + to `ureact/adaptor/join_with.hpp` + - extract `ureact::changed_to` from `ureact/adaptor/changed.hpp` + to `ureact/adaptor/changed_to.hpp` + - extract `ureact::keys` and `ureact::values` + from `ureact/adaptor/elements.hpp` + to `ureact/adaptor/keys.hpp` and `ureact/adaptor/values.hpp` +- BREAKING! #120 Rework observers + - observable_node is no longer owns observers, and it is removed at all ( + node_base is used instead) + - observer nodes own their subjects + - scoped_observer is removed + - observer owns its observer node and nothing else + - tap nodes are introduced to own both observer and observed node + - result of observe should no longer discarded for both L-value and R-value + subjects + - See #120 for examples and problem description +- #113 Add observe_policy, so even current signal values can be notified if + needed + ## [0.13.0](https://github.com/YarikTH/ureact/releases/tag/0.13.0) (2023-06-17) [Full Changelog](https://github.com/YarikTH/ureact/compare/0.12.0...0.13.0) diff --git a/include/ureact/version.hpp b/include/ureact/version.hpp index 445f89c..cd97d60 100644 --- a/include/ureact/version.hpp +++ b/include/ureact/version.hpp @@ -13,7 +13,7 @@ #define UREACT_VERSION_MAJOR 0 #define UREACT_VERSION_MINOR 14 #define UREACT_VERSION_PATCH 0 -#define UREACT_VERSION_STR "0.14.0 wip" +#define UREACT_VERSION_STR "0.14.0" #define UREACT_VERSION \ ( UREACT_VERSION_MAJOR * 10000 + UREACT_VERSION_MINOR * 100 + UREACT_VERSION_PATCH ) diff --git a/single_include/ureact/ureact_amalgamated.hpp b/single_include/ureact/ureact_amalgamated.hpp index 7ca2114..1c5719d 100644 --- a/single_include/ureact/ureact_amalgamated.hpp +++ b/single_include/ureact/ureact_amalgamated.hpp @@ -9,8 +9,8 @@ // http://www.boost.org/LICENSE_1_0.txt) // // ---------------------------------------------------------------- -// Ureact v0.14.0 wip -// Generated: 2023-06-18 13:09:41.008488 +// Ureact v0.14.0 +// Generated: 2023-07-16 17:41:26.791666 // ---------------------------------------------------------------- // ureact - C++ header-only FRP library // The library is heavily influenced by cpp.react - https://github.com/snakster/cpp.react @@ -34,7 +34,7 @@ #define UREACT_VERSION_MAJOR 0 #define UREACT_VERSION_MINOR 14 #define UREACT_VERSION_PATCH 0 -#define UREACT_VERSION_STR "0.14.0 wip" +#define UREACT_VERSION_STR "0.14.0" #define UREACT_VERSION \ ( UREACT_VERSION_MAJOR * 10000 + UREACT_VERSION_MINOR * 100 + UREACT_VERSION_PATCH ) @@ -191,8 +191,8 @@ static_assert( __cplusplus >= 201703L, "At least c++17 standard is required" ); #endif //UREACT_DETAIL_DEFINES_HPP -#ifndef UREACT_TYPE_TRAITS_HPP -#define UREACT_TYPE_TRAITS_HPP +#ifndef UREACT_UTILITY_TYPE_TRAITS_HPP +#define UREACT_UTILITY_TYPE_TRAITS_HPP #include @@ -517,7 +517,7 @@ inline constexpr bool is_reactive_v = is_reactive::value; UREACT_END_NAMESPACE -#endif //UREACT_TYPE_TRAITS_HPP +#endif //UREACT_UTILITY_TYPE_TRAITS_HPP UREACT_BEGIN_NAMESPACE @@ -759,217 +759,6 @@ UREACT_END_NAMESPACE #endif //UREACT_DETAIL_LINKER_FUNCTOR_HPP -#ifndef UREACT_EVENT_EMITTER_HPP -#define UREACT_EVENT_EMITTER_HPP - -#include - - -#ifndef UREACT_UNIT_HPP -#define UREACT_UNIT_HPP - - -UREACT_BEGIN_NAMESPACE - -/*! - * @brief This class is used as value type of unit streams, which emit events without any value other than the fact that they occurred - * - * See std::monostate https://en.cppreference.com/w/cpp/utility/variant/monostate - * See Regular Void https://open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0146r1.html#ThinkingAboutVoid - */ -struct unit -{ - constexpr unit() = default; - constexpr unit( const unit& ) = default; - constexpr unit& operator=( const unit& ) = default; - - // unit can be constructed from any value - template - explicit constexpr unit( T&& ) noexcept // NOLINT(bugprone-forwarding-reference-overload) - {} -}; - -// clang-format off -constexpr bool operator==(unit, unit) noexcept { return true; } -constexpr bool operator!=(unit, unit) noexcept { return false; } -constexpr bool operator< (unit, unit) noexcept { return false; } -constexpr bool operator> (unit, unit) noexcept { return false; } -constexpr bool operator<=(unit, unit) noexcept { return true; } -constexpr bool operator>=(unit, unit) noexcept { return true; } -//constexpr std::strong_ordering operator<=>(unit, unit) noexcept { return std::strong_ordering::equal; } -// clang-format on - -UREACT_END_NAMESPACE - -#endif //UREACT_UNIT_HPP - -UREACT_BEGIN_NAMESPACE - -/*! - * @brief Represents output stream of events. - * - * It is std::back_insert_iterator analog. - * Additionally to std::back_insert_iterator interface it provides operator<< overload - */ -template -class event_emitter final -{ -public: - using container_type = std::vector; - using iterator_category = std::output_iterator_tag; - using difference_type = std::ptrdiff_t; - using value_type = E; - using pointer = value_type*; - using reference = value_type&; - - /*! - * @brief Constructor - */ - explicit event_emitter( container_type& container ) - : m_container( &container ) - {} - - // clang-format off - event_emitter& operator*() { return *this; } - event_emitter& operator++() { return *this; } - event_emitter operator++( int ) { return *this; } // NOLINT - // clang-format on - - /*! - * @brief Adds e to the queue of outgoing events - */ - event_emitter& operator=( const E& e ) - { - m_container->push_back( e ); - return *this; - } - - /*! - * @brief Adds e to the queue of outgoing events - * - * Specialization of operator=(const E& e) for rvalue - */ - event_emitter& operator=( E&& e ) - { - m_container->push_back( std::move( e ) ); - return *this; - } - - /*! - * @brief Adds e to the queue of outgoing events - */ - event_emitter& operator<<( const E& e ) - { - m_container->push_back( e ); - return *this; - } - - /*! - * @brief Adds e to the queue of outgoing events - * - * Specialization of operator<<(const E& e) for rvalue - */ - event_emitter& operator<<( E&& e ) - { - m_container->push_back( std::move( e ) ); - return *this; - } - -private: - container_type* m_container; -}; - -UREACT_END_NAMESPACE - -#endif //UREACT_EVENT_EMITTER_HPP - -#ifndef UREACT_EVENT_RANGE_HPP -#define UREACT_EVENT_RANGE_HPP - -#include - - -UREACT_BEGIN_NAMESPACE - -/*! - * @brief Represents a range of events. It it serves as an adaptor to the underlying event container of a source node - * - * An instance of event_range holds a reference to the wrapped container and selectively exposes functionality - * that allows to iterate over its events without modifying it. - * - * TODO: think about making values movable, so values would be processed more efficiently - */ -template -class event_range final -{ -public: - using const_iterator = typename std::vector::const_iterator; - using const_reverse_iterator = typename std::vector::const_reverse_iterator; - using size_type = typename std::vector::size_type; - - /*! - * @brief Constructor - */ - explicit event_range( const std::vector& data ) - : m_data( data ) - {} - - /*! - * @brief Returns a random access const iterator to the beginning - */ - UREACT_WARN_UNUSED_RESULT const_iterator begin() const - { - return m_data.begin(); - } - - /*! - * @brief Returns a random access const iterator to the end - */ - UREACT_WARN_UNUSED_RESULT const_iterator end() const - { - return m_data.end(); - } - - /*! - * @brief Returns a reverse random access const iterator to the beginning - */ - UREACT_WARN_UNUSED_RESULT const_reverse_iterator rbegin() const - { - return m_data.rbegin(); - } - - /*! - * @brief Returns a reverse random access const iterator to the end - */ - UREACT_WARN_UNUSED_RESULT const_reverse_iterator rend() const - { - return m_data.rend(); - } - - /*! - * @brief Returns the number of events - */ - UREACT_WARN_UNUSED_RESULT size_type size() const - { - return m_data.size(); - } - - /*! - * @brief Checks whether the container is empty - */ - UREACT_WARN_UNUSED_RESULT bool empty() const - { - return m_data.empty(); - } - -private: - const std::vector& m_data; -}; - -UREACT_END_NAMESPACE - -#endif //UREACT_EVENT_RANGE_HPP - #ifndef UREACT_EVENTS_HPP #define UREACT_EVENTS_HPP @@ -1135,7 +924,7 @@ class node_id return m_context_id; } - operator value_type() // NOLINT + operator value_type() const // NOLINT { return m_id; } @@ -1221,15 +1010,11 @@ struct reactive_node_interface {} }; -class observer_interface +struct observer_interface { -public: virtual ~observer_interface() = default; -private: virtual void detach_observer() = 0; - - friend class observable; }; } // namespace detail @@ -1309,7 +1094,6 @@ namespace detail /// A simple slot map /// insert returns the slot index, which stays valid until the element is erased -/// TODO: test it thoroughly template class slot_map { @@ -1328,17 +1112,44 @@ class slot_map reset(); } + /// Custom move constructor + slot_map( slot_map&& other ) + : m_storage( std::move( other.m_storage ) ) + , m_free_indices( std::move( other.m_free_indices ) ) + , m_size( other.m_size ) + , m_capacity( other.m_capacity ) + { + other.m_size = 0; + other.m_capacity = 0; + } + + /// Custom move assign + slot_map& operator=( slot_map&& other ) + { + if( &other != this ) + { + reset(); + + m_storage = std::move( other.m_storage ); + m_free_indices = std::move( other.m_free_indices ); + m_size = other.m_size; + m_capacity = other.m_capacity; + other.m_size = 0; + other.m_capacity = 0; + } + return *this; + } + UREACT_MAKE_NONCOPYABLE( slot_map ); - UREACT_MAKE_MOVABLE( slot_map ); - /// Returns a reference to the element at specified slot index. No bounds checking is performed. + /// Returns a reference to the element at specified slot index reference operator[]( size_type index ) { assert( has_index( index ) ); return *at( index ); } - /// Returns a reference to the element at specified slot index. No bounds checking is performed. + /// Returns a constant reference to the element at specified slot index const_reference operator[]( size_type index ) const { assert( has_index( index ) ); @@ -1387,7 +1198,19 @@ class slot_map /// Return if there is any element inside UREACT_WARN_UNUSED_RESULT bool empty() const { - return !total_size(); + return m_size == 0; + } + + /// Return how many slots are used + UREACT_WARN_UNUSED_RESULT size_type size() const + { + return m_size; + } + + /// Return how many slots are allocated + UREACT_WARN_UNUSED_RESULT size_type capacity() const + { + return m_capacity; } /// Clear the data, leave capacity intact @@ -1444,9 +1267,32 @@ class slot_map : m_data{ std::make_unique( amount ) } {} + /// Custom move constructor + free_indices( free_indices&& other ) + : m_data( std::move( other.m_data ) ) + , m_size( other.m_size ) + { + other.m_size = 0; + } + + /// Custom move assign + free_indices& operator=( free_indices&& other ) + { + if( &other != this ) + { + reset(); + + m_data = std::move( other.m_data ); + m_size = other.m_size; + other.m_size = 0; + } + return *this; + } + void reset() { m_data.reset(); + m_size = 0u; } UREACT_WARN_UNUSED_RESULT size_type amount() const @@ -1587,6 +1433,11 @@ class slot_map return std::addressof( m_storage[index].data ); } + const value_type* at( size_type index ) const + { + return std::addressof( m_storage[index].data ); + } + template void construct_at( const size_type index, Args&&... args ) { @@ -1750,9 +1601,17 @@ class react_graph void propagate(); - void recalculate_successor_levels( node_data& node ); + void recalculate_successor_levels( node_data& parentNode ); + + void schedule_node( node_id nodeId ); + + void re_schedule_node( node_id nodeId ); + + void schedule_successors( node_data& parentNode ); - void schedule_successors( node_data& node ); + void propagate_node_change( node_id nodeId ); + + void finalize_changed_nodes(); void unregister_queued_nodes(); @@ -1766,8 +1625,6 @@ class react_graph bool m_propagation_is_in_progress = false; - node_id_vector m_changed_inputs; - // local to propagate. Moved here to not reallocate node_id_vector m_changed_nodes; @@ -1780,7 +1637,6 @@ inline react_graph::~react_graph() assert( m_scheduled_nodes.empty() ); assert( m_transaction_level == 0 ); assert( m_propagation_is_in_progress == false ); - assert( m_changed_inputs.empty() ); assert( m_changed_nodes.empty() ); assert( m_nodes_queued_for_unregister.empty() ); } @@ -1791,16 +1647,16 @@ inline node_id react_graph::register_node() } inline void react_graph::register_node_ptr( - node_id nodeId, const std::weak_ptr& nodePtr ) + const node_id nodeId, const std::weak_ptr& nodePtr ) { assert( nodeId.context_id() == m_id ); assert( nodePtr.use_count() > 0 ); - auto& node = m_node_data[nodeId]; + node_data& node = m_node_data[nodeId]; node.node_ptr = nodePtr; } -inline void react_graph::unregister_node( node_id nodeId ) +inline void react_graph::unregister_node( const node_id nodeId ) { assert( nodeId.context_id() == m_id ); assert( m_node_data[nodeId].successors.empty() ); @@ -1811,43 +1667,38 @@ inline void react_graph::unregister_node( node_id nodeId ) m_nodes_queued_for_unregister.add( nodeId ); } -inline void react_graph::attach_node( node_id nodeId, node_id parentId ) +inline void react_graph::attach_node( const node_id nodeId, const node_id parentId ) { assert( nodeId.context_id() == m_id ); assert( parentId.context_id() == m_id ); - auto& node = m_node_data[nodeId]; - auto& parent = m_node_data[parentId]; + node_data& node = m_node_data[nodeId]; + node_data& parent = m_node_data[parentId]; parent.successors.add( nodeId ); - if( node.level <= parent.level ) - { - node.level = parent.level + 1; - } + node.level = std::max( node.level, parent.level + 1 ); } -inline void react_graph::detach_node( node_id nodeId, node_id parentId ) +inline void react_graph::detach_node( const node_id nodeId, const node_id parentId ) { assert( nodeId.context_id() == m_id ); assert( parentId.context_id() == m_id ); - auto& parent = m_node_data[parentId]; - auto& successors = parent.successors; + node_data& parent = m_node_data[parentId]; + node_id_vector& successors = parent.successors; successors.remove( nodeId ); } -inline void react_graph::push_input( node_id nodeId ) +inline void react_graph::push_input( const node_id nodeId ) { assert( !m_propagation_is_in_progress ); - m_changed_inputs.add( nodeId ); + schedule_node( nodeId ); if( m_transaction_level == 0 ) - { propagate(); - } } inline node_id::context_id_type react_graph::create_context_id() @@ -1865,120 +1716,101 @@ inline void react_graph::propagate() { m_propagation_is_in_progress = true; - // Fill update queue with successors of changed inputs - for( node_id nodeId : m_changed_inputs ) - { - auto& node = m_node_data[nodeId]; - if( auto nodePtr = node.node_ptr.lock() ) - { - const update_result result = nodePtr->update(); - - if( result == update_result::changed ) - { - m_changed_nodes.add( nodeId ); - schedule_successors( node ); - } - else - { - assert( result == update_result::unchanged ); - } - } - } - m_changed_inputs.clear(); - - // Propagate changes while( m_scheduled_nodes.fetch_next() ) - { - for( node_id nodeId : m_scheduled_nodes.next_values() ) - { - auto& node = m_node_data[nodeId]; - if( auto nodePtr = node.node_ptr.lock() ) - { - // A predecessor of this node has shifted to a lower level? - if( node.level < node.new_level ) - { - // Re-schedule this node - node.level = node.new_level; - - recalculate_successor_levels( node ); - m_scheduled_nodes.push( nodeId, node.level ); - continue; - } + for( const node_id nodeId : m_scheduled_nodes.next_values() ) + propagate_node_change( nodeId ); - const update_result result = nodePtr->update(); + finalize_changed_nodes(); - // Topology changed? - if( result == update_result::shifted ) - { - // Re-schedule this node - recalculate_successor_levels( node ); - m_scheduled_nodes.push( nodeId, node.level ); - continue; - } + m_propagation_is_in_progress = false; - if( result == update_result::changed ) - { - m_changed_nodes.add( nodeId ); - schedule_successors( node ); - } - } + unregister_queued_nodes(); +} - node.queued = false; - } +inline void react_graph::recalculate_successor_levels( node_data& parentNode ) +{ + for( const node_id successorId : parentNode.successors ) + { + node_data& successorNode = m_node_data[successorId]; + successorNode.new_level = std::max( successorNode.new_level, parentNode.level + 1 ); } +} - // Cleanup buffers in changed nodes etc - for( node_id nodeId : m_changed_nodes ) +inline void react_graph::schedule_node( const node_id nodeId ) +{ + node_data& node = m_node_data[nodeId]; + + if( !node.queued ) { - auto& node = m_node_data[nodeId]; - if( auto nodePtr = node.node_ptr.lock() ) - { - nodePtr->finalize(); - } + node.queued = true; + m_scheduled_nodes.push( nodeId, node.level ); } - m_changed_nodes.clear(); - - // TODO: think about allowing adding new inputs and looping for them - assert( m_changed_inputs.empty() ); +} - m_propagation_is_in_progress = false; +inline void react_graph::re_schedule_node( const node_id nodeId ) +{ + node_data& node = m_node_data[nodeId]; + recalculate_successor_levels( node ); + m_scheduled_nodes.push( nodeId, node.level ); +} - unregister_queued_nodes(); +inline void react_graph::schedule_successors( node_data& parentNode ) +{ + for( const node_id successorId : parentNode.successors ) + schedule_node( successorId ); } -inline void react_graph::recalculate_successor_levels( node_data& node ) +inline void react_graph::propagate_node_change( const node_id nodeId ) { - for( node_id successorId : node.successors ) + node_data& node = m_node_data[nodeId]; + if( std::shared_ptr nodePtr = node.node_ptr.lock() ) { - auto& successor = m_node_data[successorId]; + // A predecessor of this node has shifted to a lower level? + if( node.level < node.new_level ) + { + node.level = node.new_level; + re_schedule_node( nodeId ); + return; + } + + const update_result result = nodePtr->update(); - if( successor.new_level <= node.level ) + // Topology changed? + if( result == update_result::shifted ) { - successor.new_level = node.level + 1; + re_schedule_node( nodeId ); + return; + } + + if( result == update_result::changed ) + { + m_changed_nodes.add( nodeId ); + schedule_successors( node ); } } + + node.queued = false; } -inline void react_graph::schedule_successors( node_data& node ) +inline void react_graph::finalize_changed_nodes() { - // add children to queue - for( node_id successorId : node.successors ) + // Cleanup buffers in changed nodes etc + for( const node_id nodeId : m_changed_nodes ) { - auto& successor = m_node_data[successorId]; - - if( !successor.queued ) + node_data& node = m_node_data[nodeId]; + if( std::shared_ptr nodePtr = node.node_ptr.lock() ) { - successor.queued = true; - m_scheduled_nodes.push( successorId, successor.level ); + nodePtr->finalize(); } } + m_changed_nodes.clear(); } inline void react_graph::unregister_queued_nodes() { assert( !m_propagation_is_in_progress ); - for( node_id nodeId : m_nodes_queued_for_unregister ) + for( const node_id nodeId : m_nodes_queued_for_unregister ) unregister_node( nodeId ); m_nodes_queued_for_unregister.clear(); } @@ -1990,13 +1822,8 @@ UREACT_WARN_UNUSED_RESULT inline bool react_graph::topological_queue::fetch_next // Find min level of nodes in queue data auto minimal_level = std::numeric_limits::max(); - for( const auto& e : m_queue_data ) - { - if( minimal_level > e.second ) - { - minimal_level = e.second; - } - } + for( const entry& e : m_queue_data ) + minimal_level = std::min( minimal_level, e.second ); // Swap entries with min level to the end const auto p = detail::partition( m_queue_data.begin(), @@ -2009,9 +1836,7 @@ UREACT_WARN_UNUSED_RESULT inline bool react_graph::topological_queue::fetch_next // Move min level values to next data for( auto it = p, ite = m_queue_data.end(); it != ite; ++it ) - { m_next_data.push_back( it->first ); - } // Truncate moved entries const auto to_resize = static_cast( std::distance( m_queue_data.begin(), p ) ); @@ -2117,16 +1942,6 @@ class context final : protected detail::context_internals {} }; -UREACT_END_NAMESPACE - -#endif //UREACT_CONTEXT_HPP - -#ifndef UREACT_DEFAULT_CONTEXT_HPP -#define UREACT_DEFAULT_CONTEXT_HPP - - -UREACT_BEGIN_NAMESPACE - namespace default_context { @@ -2152,11 +1967,7 @@ inline context get() UREACT_END_NAMESPACE -#endif //UREACT_DEFAULT_CONTEXT_HPP - -#ifndef UREACT_DETAIL_OBSERVABLE_NODE_HPP -#define UREACT_DETAIL_OBSERVABLE_NODE_HPP - +#endif //UREACT_CONTEXT_HPP #ifndef UREACT_DETAIL_NODE_BASE_HPP #define UREACT_DETAIL_NODE_BASE_HPP @@ -2287,63 +2098,130 @@ UREACT_END_NAMESPACE #endif // UREACT_DETAIL_NODE_BASE_HPP +#ifndef UREACT_UTILITY_EVENT_RANGE_HPP +#define UREACT_UTILITY_EVENT_RANGE_HPP + +#include + + +#ifndef UREACT_UTILITY_UNIT_HPP +#define UREACT_UTILITY_UNIT_HPP + + UREACT_BEGIN_NAMESPACE -namespace detail +/*! + * @brief This class is used as value type of unit streams, which emit events without any value other than the fact that they occurred + * + * See std::monostate https://en.cppreference.com/w/cpp/utility/variant/monostate + * See Regular Void https://open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0146r1.html#ThinkingAboutVoid + */ +struct unit { + constexpr unit() = default; + constexpr unit( const unit& ) = default; + constexpr unit& operator=( const unit& ) = default; + + // unit can be constructed from any value + template + explicit constexpr unit( T&& ) noexcept // NOLINT(bugprone-forwarding-reference-overload) + {} +}; + +// clang-format off +constexpr bool operator==(unit, unit) noexcept { return true; } +constexpr bool operator!=(unit, unit) noexcept { return false; } +constexpr bool operator< (unit, unit) noexcept { return false; } +constexpr bool operator> (unit, unit) noexcept { return false; } +constexpr bool operator<=(unit, unit) noexcept { return true; } +constexpr bool operator>=(unit, unit) noexcept { return true; } +//constexpr std::strong_ordering operator<=>(unit, unit) noexcept { return std::strong_ordering::equal; } +// clang-format on -class observable +UREACT_END_NAMESPACE + +#endif //UREACT_UTILITY_UNIT_HPP + +UREACT_BEGIN_NAMESPACE + +/*! + * @brief Represents a range of events. It is serves as an adaptor to the underlying event container of a source node + * + * An instance of event_range holds a reference to the wrapped container and selectively exposes functionality + * that allows to iterate over its events without modifying it. + * + * TODO: think about making values movable, so values would be processed more efficiently + */ +template +class event_range final { public: - observable() = default; + using const_iterator = typename std::vector::const_iterator; + using const_reverse_iterator = typename std::vector::const_reverse_iterator; + using size_type = typename std::vector::size_type; + + /*! + * @brief Constructor + */ + explicit event_range( const std::vector& data ) + : m_data( data ) + {} + + /*! + * @brief Returns a random access const iterator to the beginning + */ + UREACT_WARN_UNUSED_RESULT const_iterator begin() const + { + return m_data.begin(); + } - UREACT_MAKE_NONCOPYABLE( observable ); - UREACT_MAKE_NONMOVABLE( observable ); + /*! + * @brief Returns a random access const iterator to the end + */ + UREACT_WARN_UNUSED_RESULT const_iterator end() const + { + return m_data.end(); + } - ~observable() + /*! + * @brief Returns a reverse random access const iterator to the beginning + */ + UREACT_WARN_UNUSED_RESULT const_reverse_iterator rbegin() const { - for( const auto& p : m_observers ) - if( p != nullptr ) - p->detach_observer(); + return m_data.rbegin(); } - void register_observer( std::shared_ptr&& obs_ptr ) + /*! + * @brief Returns a reverse random access const iterator to the end + */ + UREACT_WARN_UNUSED_RESULT const_reverse_iterator rend() const { - m_observers.push_back( std::move( obs_ptr ) ); + return m_data.rend(); } - void unregister_observer( observer_interface* raw_obs_ptr ) + /*! + * @brief Returns the number of events + */ + UREACT_WARN_UNUSED_RESULT size_type size() const { - for( auto it = m_observers.begin(); it != m_observers.end(); ++it ) - { - if( it->get() == raw_obs_ptr ) - { - it->get()->detach_observer(); - m_observers.erase( it ); - break; - } - } + return m_data.size(); } -private: - std::vector> m_observers; -}; + /*! + * @brief Checks whether the container is empty + */ + UREACT_WARN_UNUSED_RESULT bool empty() const + { + return m_data.empty(); + } -class observable_node - : public node_base - , public observable -{ -public: - explicit observable_node( const context& context ) - : node_base( context ) - {} +private: + const std::vector& m_data; }; -} // namespace detail - UREACT_END_NAMESPACE -#endif // UREACT_DETAIL_OBSERVABLE_NODE_HPP +#endif //UREACT_UTILITY_EVENT_RANGE_HPP UREACT_BEGIN_NAMESPACE @@ -2351,13 +2229,13 @@ namespace detail { template -class event_stream_node : public observable_node +class event_stream_node : public node_base { public: using event_value_list = std::vector; explicit event_stream_node( const context& context ) - : observable_node( context ) + : node_base( context ) {} event_value_list& get_events() @@ -2885,23 +2763,109 @@ UREACT_WARN_UNUSED_RESULT auto make_source() -> event_source return make_source( default_context::get() ); } -/*! - * @brief Create a new events node and links it to the returned events instance - */ -template -UREACT_WARN_UNUSED_RESULT auto make_never() -> events -{ - return make_never( default_context::get() ); -} +/*! + * @brief Create a new events node and links it to the returned events instance + */ +template +UREACT_WARN_UNUSED_RESULT auto make_never() -> events +{ + return make_never( default_context::get() ); +} + +} // namespace default_context + +UREACT_END_NAMESPACE + +#endif //UREACT_EVENTS_HPP + +#ifndef UREACT_UTILITY_EVENT_EMITTER_HPP +#define UREACT_UTILITY_EVENT_EMITTER_HPP + +#include + + +UREACT_BEGIN_NAMESPACE + +/*! + * @brief Represents output stream of events. + * + * It is std::back_insert_iterator analog. + * Additionally to std::back_insert_iterator interface it provides operator<< overload + */ +template +class event_emitter final +{ +public: + using container_type = std::vector; + using iterator_category = std::output_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = E; + using pointer = value_type*; + using reference = value_type&; + + /*! + * @brief Constructor + */ + explicit event_emitter( container_type& container ) + : m_container( &container ) + {} + + // clang-format off + event_emitter& operator*() { return *this; } + event_emitter& operator++() { return *this; } + event_emitter operator++( int ) { return *this; } // NOLINT + // clang-format on + + /*! + * @brief Adds e to the queue of outgoing events + */ + event_emitter& operator=( const E& e ) + { + m_container->push_back( e ); + return *this; + } + + /*! + * @brief Adds e to the queue of outgoing events + * + * Specialization of operator=(const E& e) for rvalue + */ + event_emitter& operator=( E&& e ) + { + m_container->push_back( std::move( e ) ); + return *this; + } + + /*! + * @brief Adds e to the queue of outgoing events + */ + event_emitter& operator<<( const E& e ) + { + m_container->push_back( e ); + return *this; + } + + /*! + * @brief Adds e to the queue of outgoing events + * + * Specialization of operator<<(const E& e) for rvalue + */ + event_emitter& operator<<( E&& e ) + { + m_container->push_back( std::move( e ) ); + return *this; + } -} // namespace default_context +private: + container_type* m_container; +}; UREACT_END_NAMESPACE -#endif //UREACT_EVENTS_HPP +#endif //UREACT_UTILITY_EVENT_EMITTER_HPP -#ifndef UREACT_SIGNAL_PACK_HPP -#define UREACT_SIGNAL_PACK_HPP +#ifndef UREACT_UTILITY_SIGNAL_PACK_HPP +#define UREACT_UTILITY_SIGNAL_PACK_HPP #include @@ -2951,7 +2915,7 @@ UREACT_WARN_UNUSED_RESULT auto with( const signal&... deps ) UREACT_END_NAMESPACE -#endif //UREACT_SIGNAL_PACK_HPP +#endif //UREACT_UTILITY_SIGNAL_PACK_HPP UREACT_BEGIN_NAMESPACE @@ -3218,55 +3182,6 @@ UREACT_END_NAMESPACE #define UREACT_ADAPTOR_CHANGED_HPP -#ifndef UREACT_ADAPTOR_FILTER_HPP -#define UREACT_ADAPTOR_FILTER_HPP - - -UREACT_BEGIN_NAMESPACE - -namespace detail -{ - -struct FilterAdaptor : SyncedAdaptorBase -{ - /*! - * @brief Create a new event stream that filters events from other stream - * - * For every event e in source, emit e if pred(e, deps...) == true. - * Synchronized values of signals in dep_pack are passed to op as additional arguments. - * - * The signature of pred should be equivalent to: - * * bool pred(const E&, const Deps& ...) - * - * Semantically equivalent of ranges::filter - * - * @note Changes of signals in dep_pack do not trigger an update - only received events do - */ - template - UREACT_WARN_UNUSED_RESULT constexpr auto operator()( - const events& source, const signal_pack& dep_pack, Pred&& pred ) const - { - return process( source, - dep_pack, // - [pred = std::forward( pred )]( - event_range range, const Deps... deps, event_emitter out ) mutable { - for( const auto& e : range ) - if( std::invoke( pred, e, deps... ) ) - out << e; - } ); - } - - using SyncedAdaptorBase::operator(); -}; - -} // namespace detail - -inline constexpr detail::FilterAdaptor filter; - -UREACT_END_NAMESPACE - -#endif // UREACT_ADAPTOR_FILTER_HPP - #ifndef UREACT_ADAPTOR_MONITOR_HPP #define UREACT_ADAPTOR_MONITOR_HPP @@ -3275,8 +3190,8 @@ UREACT_END_NAMESPACE #define UREACT_SIGNAL_HPP -#ifndef UREACT_HAS_CHANGED_HPP -#define UREACT_HAS_CHANGED_HPP +#ifndef UREACT_DETAIL_HAS_CHANGED_HPP +#define UREACT_DETAIL_HAS_CHANGED_HPP #include #include @@ -3353,7 +3268,7 @@ inline constexpr has_changed_detail::HasChangedCPO has_changed{}; UREACT_END_NAMESPACE -#endif //UREACT_HAS_CHANGED_HPP +#endif //UREACT_DETAIL_HAS_CHANGED_HPP UREACT_BEGIN_NAMESPACE @@ -3361,16 +3276,16 @@ namespace detail { template -class signal_node : public observable_node +class signal_node : public node_base { public: explicit signal_node( const context& context ) - : observable_node( context ) + : node_base( context ) {} template signal_node( const context& context, T&& value ) - : observable_node( context ) + : node_base( context ) , m_value( std::forward( value ) ) {} @@ -4068,6 +3983,72 @@ UREACT_END_NAMESPACE UREACT_BEGIN_NAMESPACE +/*! + * @brief Emits unit when target signal was changed + * + * Creates a unit stream that emits when target is changed. + */ +inline constexpr auto changed = monitor | unify; + +UREACT_END_NAMESPACE + +#endif // UREACT_ADAPTOR_CHANGED_HPP + +#ifndef UREACT_ADAPTOR_CHANGED_TO_HPP +#define UREACT_ADAPTOR_CHANGED_TO_HPP + + +#ifndef UREACT_ADAPTOR_FILTER_HPP +#define UREACT_ADAPTOR_FILTER_HPP + + +UREACT_BEGIN_NAMESPACE + +namespace detail +{ + +struct FilterAdaptor : SyncedAdaptorBase +{ + /*! + * @brief Create a new event stream that filters events from other stream + * + * For every event e in source, emit e if pred(e, deps...) == true. + * Synchronized values of signals in dep_pack are passed to op as additional arguments. + * + * The signature of pred should be equivalent to: + * * bool pred(const E&, const Deps& ...) + * + * Semantically equivalent of ranges::filter + * + * @note Changes of signals in dep_pack do not trigger an update - only received events do + */ + template + UREACT_WARN_UNUSED_RESULT constexpr auto operator()( + const events& source, const signal_pack& dep_pack, Pred&& pred ) const + { + return process( source, + dep_pack, // + [pred = std::forward( pred )]( + event_range range, const Deps... deps, event_emitter out ) mutable { + for( const auto& e : range ) + if( std::invoke( pred, e, deps... ) ) + out << e; + } ); + } + + using SyncedAdaptorBase::operator(); +}; + +} // namespace detail + +inline constexpr detail::FilterAdaptor filter; + +UREACT_END_NAMESPACE + +#endif // UREACT_ADAPTOR_FILTER_HPP + +UREACT_BEGIN_NAMESPACE + namespace detail { @@ -4088,13 +4069,6 @@ struct ChangedToAdaptor : Adaptor } // namespace detail -/*! - * @brief Emits unit when target signal was changed - * - * Creates a unit stream that emits when target is changed. - */ -inline constexpr auto changed = monitor | unify; - /*! * @brief Emits unit when target signal was changed to value * Creates a unit stream that emits when target is changed and 'target.get() == value'. @@ -4104,7 +4078,7 @@ inline constexpr detail::ChangedToAdaptor changed_to; UREACT_END_NAMESPACE -#endif // UREACT_ADAPTOR_CHANGED_HPP +#endif // UREACT_ADAPTOR_CHANGED_TO_HPP #ifndef UREACT_ADAPTOR_COLLECT_HPP #define UREACT_ADAPTOR_COLLECT_HPP @@ -4344,6 +4318,13 @@ UREACT_END_NAMESPACE #endif // UREACT_ADAPTOR_FOLD_HPP +#ifndef UREACT_DETAIL_CONTAINER_TYPE_TRAITS_HPP +#define UREACT_DETAIL_CONTAINER_TYPE_TRAITS_HPP + +#include +#include // for std::begin(). It is declared in core headers anyway + + UREACT_BEGIN_NAMESPACE namespace detail @@ -4376,6 +4357,26 @@ struct has_insert_method inline constexpr bool has_insert_method_v = has_insert_method::value; +template +struct container_value +{ + using type = std::decay_t() ) )>; +}; + +template +using container_value_t = typename container_value::type; + +} // namespace detail + +UREACT_END_NAMESPACE + +#endif //UREACT_DETAIL_CONTAINER_TYPE_TRAITS_HPP + +UREACT_BEGIN_NAMESPACE + +namespace detail +{ + template