A model/subscribe helper-class OBSERVABLE to common

This commit is contained in:
decimad 2016-12-22 22:01:05 +01:00 committed by Maciej Suminski
parent 2a8dd508c4
commit bbaeeceeac
3 changed files with 398 additions and 1 deletions

View File

@ -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

189
common/observable.cpp Normal file
View File

@ -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<void*>( nullptr ) );
observers_.erase( it, observers_.end() );
}
}
LINK::LINK()
{
}
LINK::LINK( std::shared_ptr<DETAIL::OBSERVABLE_BASE::IMPL> 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<IMPL>( this );
}
}
void OBSERVABLE_BASE::allocate_shared_impl()
{
if(!impl_) {
impl_ = std::make_shared<IMPL>();
}
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::IMPL> OBSERVABLE_BASE::get_shared_impl()
{
allocate_shared_impl();
return impl_;
}
void OBSERVABLE_BASE::add_observer( void* observer ) {
allocate_impl();
impl_->observers_.push_back( observer );
}
}
}

208
include/observable.h Normal file
View File

@ -0,0 +1,208 @@
#pragma once
#include <cassert>
#include <memory>
#include <vector>
#include <algorithm>
#include <functional>
/*
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<void*> observers_;
unsigned int iteration_count_;
OBSERVABLE_BASE* owned_by_;
};
void allocate_impl();
void allocate_shared_impl();
void deallocate_impl();
std::shared_ptr<IMPL> 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> impl_;
};
}
//
// Simple RAII-handle to a subscription.
//
class LINK {
public:
LINK();
LINK( std::shared_ptr<DETAIL::OBSERVABLE_BASE::IMPL> 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<DETAIL::OBSERVABLE_BASE::IMPL> 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<void*>(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<void*>(aObserver) );
return LINK( impl_, static_cast<void*>(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<void*>(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<ObserverInterface*>(void_ptr);
(typed_ptr->*Ptr)(std::forward<Args2>( 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<ObserverInterface*>(void_ptr);
(typed_ptr->*Ptr)(std::forward<Args2>( aArgs )...);
}
}
}
catch(...) {
leave_iteration();
throw;
}
leave_iteration();
}
}
};
}