From bbaeeceeac7a1b1725e4e800ebbbde4503e5406b Mon Sep 17 00:00:00 2001 From: decimad Date: Thu, 22 Dec 2016 22:01:05 +0100 Subject: [PATCH] A model/subscribe helper-class OBSERVABLE to common --- common/CMakeLists.txt | 2 +- common/observable.cpp | 189 ++++++++++++++++++++++++++++++++++++++ include/observable.h | 208 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 398 insertions(+), 1 deletion(-) create mode 100644 common/observable.cpp create mode 100644 include/observable.h diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index b27107da68..6e553b7168 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -56,7 +56,6 @@ set( GAL_SRCS ) add_library( gal STATIC ${GAL_SRCS} ) -add_dependencies( gal shader_headers ) add_dependencies( gal lib-dependencies ) target_link_libraries( gal @@ -237,6 +236,7 @@ set( COMMON_SRCS lockfile.cpp msgpanel.cpp netlist_keywords.cpp + observable.cpp prependpath.cpp project.cpp properties.cpp diff --git a/common/observable.cpp b/common/observable.cpp new file mode 100644 index 0000000000..b4b844fbfc --- /dev/null +++ b/common/observable.cpp @@ -0,0 +1,189 @@ +#include "observable.h" + +namespace UTIL { + + namespace DETAIL { + + template< typename T > + struct equals { + equals( const T& val ) : val_( val ) {} + + bool operator()( const T& val ) { + return val == val_; + } + + private: + const T& val_; + }; + + OBSERVABLE_BASE::IMPL::IMPL( OBSERVABLE_BASE* owned_by ) + : owned_by_( owned_by ), iteration_count_( 0 ) + {} + + bool OBSERVABLE_BASE::IMPL::is_shared() const + { + return owned_by_ == nullptr; + } + + void OBSERVABLE_BASE::IMPL::set_shared() + { + owned_by_ = nullptr; + } + + OBSERVABLE_BASE::IMPL::~IMPL() + { + } + + void OBSERVABLE_BASE::IMPL::remove_observer( void* observer ) + { + if(iteration_count_) { + for(auto*& ptr : observers_) { + if(ptr == observer) { + ptr = nullptr; + } + } + } + else { + collect(); + if(observers_.empty() && owned_by_) { + owned_by_->on_observers_empty(); + } + } + } + + void OBSERVABLE_BASE::IMPL::collect() + { + auto it = std::remove_if( observers_.begin(), observers_.end(), DETAIL::equals( nullptr ) ); + observers_.erase( it, observers_.end() ); + } + } + + LINK::LINK() + { + } + + LINK::LINK( std::shared_ptr token, void* observer ) + : token_( std::move( token ) ), observer_( observer ) + { + } + + LINK::LINK( LINK&& other ) + : token_( std::move( other.token_ ) ), observer_( other.observer_ ) + { + other.token_.reset(); + } + + LINK& LINK::operator=( LINK&& other ) + { + token_ = std::move( other.token_ ); + other.token_.reset(); + observer_ = other.observer_; + return *this; + } + + LINK::operator bool() const { + return token_ ? true : false; + } + + LINK::~LINK() + { + reset(); + } + + void LINK::reset() + { + if(token_) { + token_->remove_observer( observer_ ); + } + } + + namespace DETAIL { + + OBSERVABLE_BASE::OBSERVABLE_BASE() + { + } + + OBSERVABLE_BASE::OBSERVABLE_BASE( OBSERVABLE_BASE& other ) + : impl_( other.get_shared_impl() ) + { + } + + OBSERVABLE_BASE::~OBSERVABLE_BASE() + { + } + + size_t OBSERVABLE_BASE::size() const { + if(impl_) { + return impl_->observers_.size(); + } + else { + return 0; + } + } + + void OBSERVABLE_BASE::remove_observer( void* observer ) { + assert( impl_ ); + impl_->remove_observer( observer ); + } + + void OBSERVABLE_BASE::allocate_impl() { + if(!impl_) { + impl_ = std::make_shared( this ); + } + } + + void OBSERVABLE_BASE::allocate_shared_impl() + { + if(!impl_) { + impl_ = std::make_shared(); + } + else { + impl_->set_shared(); + } + } + + void OBSERVABLE_BASE::deallocate_impl() { + impl_.reset(); + } + + void OBSERVABLE_BASE::enter_iteration() { + if(impl_) { + ++impl_->iteration_count_; + } + } + + void OBSERVABLE_BASE::leave_iteration() { + if(impl_) { + --impl_->iteration_count_; + if(impl_->iteration_count_ == 0) { + if(!impl_->is_shared() && impl_.use_count() == 1) { + impl_.reset(); + } + else { + impl_->collect(); + } + } + } + } + + void OBSERVABLE_BASE::on_observers_empty() + { + // called by an impl that is owned by this, ie. it is a non-shared impl + // also it is not iterating + deallocate_impl(); + } + + std::shared_ptr OBSERVABLE_BASE::get_shared_impl() + { + allocate_shared_impl(); + return impl_; + } + + void OBSERVABLE_BASE::add_observer( void* observer ) { + allocate_impl(); + impl_->observers_.push_back( observer ); + } + + } + +} diff --git a/include/observable.h b/include/observable.h new file mode 100644 index 0000000000..c86fb48a26 --- /dev/null +++ b/include/observable.h @@ -0,0 +1,208 @@ +#pragma once +#include +#include +#include +#include +#include + +/* +model subscriber implementation using links to represent connections. +subscribers can be removed during notification. +if no observers are registered, size is the size of a shared_ptr. +*/ + +namespace UTIL { + + class LINK; + + namespace DETAIL { + + struct OBSERVABLE_BASE { + public: + OBSERVABLE_BASE(); + OBSERVABLE_BASE( OBSERVABLE_BASE& other ); + + ~OBSERVABLE_BASE(); + + size_t size() const; + + private: + friend class UTIL::LINK; + + struct IMPL { + IMPL( OBSERVABLE_BASE* owned_by = nullptr ); + bool is_shared() const; + void set_shared(); + ~IMPL(); + + void remove_observer( void* observer ); + void collect(); + + std::vector observers_; + unsigned int iteration_count_; + OBSERVABLE_BASE* owned_by_; + }; + + void allocate_impl(); + void allocate_shared_impl(); + + void deallocate_impl(); + + std::shared_ptr get_shared_impl(); + + protected: + void on_observers_empty(); + + void enter_iteration(); + void leave_iteration(); + + void add_observer( void* observer ); + void remove_observer( void* observer ); + + std::shared_ptr impl_; + }; + + } + + // + // Simple RAII-handle to a subscription. + // + class LINK { + public: + LINK(); + LINK( std::shared_ptr token, void* observer ); + LINK( LINK&& other ); + LINK( const LINK& ) = delete; + + void operator=( const LINK& ) = delete; + LINK& operator=( LINK&& other ); + + void reset(); + + explicit operator bool() const; + + ~LINK(); + + private: + std::shared_ptr token_; + void* observer_; + }; + + // + // + // + template< typename ObserverInterface > + class OBSERVABLE : + public DETAIL::OBSERVABLE_BASE + { + public: + /** + * Function Observable() + * Constructor. Constructs an observable with empty non-shared subscription list. + */ + OBSERVABLE() {} + + /** + * Function Observable(OBSERVABLE&) + * Constructor. Constructs an observable with a shared subscription list. + * @param aInherit Observable to share the subscription list with. + */ + OBSERVABLE( OBSERVABLE& aInherit ) + : OBSERVABLE_BASE( aInherit ) + {} + + /** + * Function SubscribeUnmanaged + * adds a subscription without RAII link. + * @param aObserver Observer to subscribe + */ + void SubscribeUnmanaged( ObserverInterface* aObserver ) + { + return OBSERVABLE_BASE::add_observer_unmanaged( static_cast(aObserver) ); + } + + /** + * Function Subscribe + * adds a subscription returning an RAII link + * @param aObserver observer to subscribe + * @return RAII link controlling the lifetime of the subscription + */ + LINK Subscribe( ObserverInterface* aObserver ) { + OBSERVABLE_BASE::add_observer( static_cast(aObserver) ); + return LINK( impl_, static_cast(aObserver) ); + } + + /** + * Function Unsubscribe + * cancels the subscription of a subscriber. Can be called during notification calls. + * @param aObserver observer to remove from the subscription list + */ + void Unsubscribe( ObserverInterface* obs ) + { + OBSERVABLE_BASE::remove_observer( static_cast(obs) ); + } + + /** + * Function Notify + * Notifies event to all subscribed observers. + * @param Ptr pointer to method of the Observer-interface + * @param Args list of arguments to each notification call, will be perfectly forwarded. + */ + template< typename... Args1, typename... Args2 > + void Notify( void(ObserverInterface::*Ptr)(Args1...), Args2&&... aArgs ) + { + static_assert(sizeof...(Args1) == sizeof...(Args2), "argument counts don't match"); + + if(impl_) { + enter_iteration(); + try { + for(auto* void_ptr : impl_->observers_) { + if(void_ptr) { + auto* typed_ptr = static_cast(void_ptr); + (typed_ptr->*Ptr)(std::forward( aArgs )...); + } + } + } + catch(...) { + leave_iteration(); + throw; + } + + leave_iteration(); + } + } + + /** + * Function Notify + * Notifies event to all subscribed observers but one to be ignore. + * @param Ptr pointer to method of the Observer-interface + * @param aIgnore observer to ignore during this notification + * @param Args list of arguments to each notification call, will be perfectly forwarded. + */ + template< typename... Args1, typename... Args2 > + void NotifyIgnore( void(ObserverInterface::*Ptr)(Args1...), ObserverInterface* aIgnore, + Args2&&... aArgs ) + { + static_assert(sizeof...(Args1) == sizeof...(Args2), "argument counts don't match"); + + if(impl_) { + enter_iteration(); + try { + for(auto* void_ptr : impl_->observers_) { + if(void_ptr && void_ptr != aIgnore) { + auto* typed_ptr = static_cast(void_ptr); + (typed_ptr->*Ptr)(std::forward( aArgs )...); + } + } + } + catch(...) { + leave_iteration(); + throw; + } + leave_iteration(); + } + } + + }; + +}