/* * ObservedValue.hpp * * Created on: Jun 19, 2015 * Author: heber */ #ifndef OBSERVEDVALUE_HPP_ #define OBSERVEDVALUE_HPP_ // include config.h #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "CodePatterns/Assert.hpp" #include "CodePatterns/Observer/Observable.hpp" #include "CodePatterns/Observer/Notification.hpp" /** This class wraps a value in another class and instance which we want to read * from in a thread-safe manner. * * The information in this class is directly derived from information in another * class that infrequently changes and whose changes are propagated through the * Observer/Observable mechanism. I.e. it is very similar to the \sa Chacheable * class, however here the information is updated directly. In other words, this * class allows for disconnecting two classes from another when they depend * oneway or bothways from information in the other class. * * A classical example is a GUI that needs to display internal data. Changes in * the GUI occur too slowly, i.e. the class owning the value being displayed * may be changed and destroyed before the GUI is actually updated. To safely * access the value, the ObservedValue is put in between two class and the GUI. * The value must be accessible via a getter and changes to the value must be * propagated via update() or recieveNotification(). * * In other words, the update is split up into two paths: a short path where * just the required minimal information is updated (e.g. new position of the * atom as \a NDIM \a Vector), and a long path where the result of the * information update is created (e.g. the atom is redrawn). The short path * must be short, i.e. require only few instructions, while the long path may * contain many and may even be taken in a lazy fashion, i.e. regardless of * the numnber of updates this path is only called every 1/20 second. * * The general building blocks are then as follows: * -# an instance with an ObservedValue \ foo as member variable * -# for convenience the instance may construct an internal references to * the entitiy that is observed for information update (and that is passed * on to ObservedValue cstor) * -# static member function \a updateFoo() - this obtains an updated value used * by \a foo upon receiving Observable's update() or recieveNotification() * (e.g. requesting getPosition() from the respective \a atom). It may be * static as it is independent of the internal state of the instance * containing \a foo * -# member function \a resetFoo() that uses ObservedValue::get() to * obtain a (thread)safe copy of the value containing all information to * internally update the instance (e.g. redraw an atom's sphere at the new * position) * -# the instance containing \a foo is subscribed to the same channels and * calls resetFoo() upon receiving the signal. * * For an example code see the respective unit test and the stub class used * there. */ template class ObservedValue : public Observer { public: ObservedValue( const Observable * const _owner, const boost::function &_recalcMethod, const std::string &_name, const T &_initialvalue, const Observable::channels_t &_channels); ObservedValue(const ObservedValue &); virtual ~ObservedValue(); // methods implemented for base-class Observer void update(Observable *publisher); void recieveNotification(Observable *publisher, Notification_ptr notification); virtual void subjectKilled(Observable *publisher); const T& get() const; private: void activateObserver(); void deactivateObserver(); private: const Observable * const owner; const boost::function recalcMethod; //!> contains list of possible channels to enlist, if empty we signOn globally const Observable::channels_t channels; //!> whether we are still signed on or not bool signedOn; //!> mutex to ensure access is only per-thread mutable boost::recursive_mutex signedOnLock; //!> mutex to ensure access is only per-thread mutable boost::recursive_mutex valueLock; //!> internal value associated with changes in Observable T value; }; template ObservedValue::ObservedValue( const Observable * const _owner, const boost::function &_recalcMethod, const std::string &_name, const T &_initialvalue, const Observable::channels_t &_channels) : Observer(_name + "(Cached)"), owner(_owner), recalcMethod(_recalcMethod), channels(_channels), signedOn(false), value(_initialvalue) { // we sign on with the best(=lowest) priority, so cached values are recalculated before // anybody else might ask for updated values activateObserver(); } // de-activated copy constructor template ObservedValue::ObservedValue(const ObservedValue&) { ASSERT(0,"ObservedValues should never be copied"); } template ObservedValue::~ObservedValue() { // make highest priorty such that ObservedValues are always updated first deactivateObserver(); } template void ObservedValue::activateObserver() { boost::lock_guard guard(signedOnLock); if ((owner != NULL) && (!signedOn)) { if (channels.empty()) { owner->signOn(this,GlobalObservableInfo::PriorityLevel(int(-20))); } else { for (Observable::channels_t::const_iterator iter = channels.begin(); iter != channels.end(); ++iter) owner->signOn(this,*iter); } signedOn = true; } } template void ObservedValue::deactivateObserver() { boost::lock_guard guard(signedOnLock); if ((owner != NULL) && (signedOn)) { if (channels.empty()) { owner->signOff(this); } else { for (Observable::channels_t::const_iterator iter = channels.begin(); iter != channels.end(); ++iter) owner->signOff(this,*iter); } signedOn = false; } } template void ObservedValue::update(Observable *publisher) { ASSERT( channels.empty(), "ObservedValue::update() - received general update although we are channel-centric."); #ifdef LOG_OBSERVER observerLog().addMessage() << "## ObservedValue " << observerLog().getName(this) << " received global update."; #endif boost::lock_guard guard(valueLock); value = recalcMethod(); } template void ObservedValue::recieveNotification(Observable *publisher, Notification_ptr notification) { ASSERT( !channels.empty(), "ObservedValue::update() - received channel update although we are global."); if (publisher == owner) { const Observable::channels_t::const_iterator iter = std::find(channels.begin(), channels.end(), notification->getChannelNo()); if (iter != channels.end()) { #ifdef LOG_OBSERVER observerLog().addMessage() << "## ObservedValue " << observerLog().getName(this) << " received local update in channel " << notification->getChannelNo() << "."; #endif boost::lock_guard guard(valueLock); value = recalcMethod(); } } } template void ObservedValue::subjectKilled(Observable *publisher) { boost::lock_guard guard(signedOnLock); if (publisher == owner) { signedOn = false; } } template const T& ObservedValue::get() const { boost::lock_guard guard(valueLock); return value; } #endif /* OBSERVEDVALUE_HPP_ */