A model/subscribe helper-class OBSERVABLE to common
This commit is contained in:
parent
2a8dd508c4
commit
bbaeeceeac
|
@ -56,7 +56,6 @@ set( GAL_SRCS
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library( gal STATIC ${GAL_SRCS} )
|
add_library( gal STATIC ${GAL_SRCS} )
|
||||||
add_dependencies( gal shader_headers )
|
|
||||||
add_dependencies( gal lib-dependencies )
|
add_dependencies( gal lib-dependencies )
|
||||||
|
|
||||||
target_link_libraries( gal
|
target_link_libraries( gal
|
||||||
|
@ -237,6 +236,7 @@ set( COMMON_SRCS
|
||||||
lockfile.cpp
|
lockfile.cpp
|
||||||
msgpanel.cpp
|
msgpanel.cpp
|
||||||
netlist_keywords.cpp
|
netlist_keywords.cpp
|
||||||
|
observable.cpp
|
||||||
prependpath.cpp
|
prependpath.cpp
|
||||||
project.cpp
|
project.cpp
|
||||||
properties.cpp
|
properties.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<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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue