1 | /*
|
---|
2 | * ObservedValue.hpp
|
---|
3 | *
|
---|
4 | * Created on: Jun 19, 2015
|
---|
5 | * Author: heber
|
---|
6 | */
|
---|
7 |
|
---|
8 | #ifndef OBSERVEDVALUE_HPP_
|
---|
9 | #define OBSERVEDVALUE_HPP_
|
---|
10 |
|
---|
11 | // include config.h
|
---|
12 | #ifdef HAVE_CONFIG_H
|
---|
13 | #include <config.h>
|
---|
14 | #endif
|
---|
15 |
|
---|
16 | #include <boost/thread/locks.hpp>
|
---|
17 | #include <boost/thread/recursive_mutex.hpp>
|
---|
18 | #include <list>
|
---|
19 |
|
---|
20 | #include "CodePatterns/Assert.hpp"
|
---|
21 |
|
---|
22 | #include "CodePatterns/Observer/Observable.hpp"
|
---|
23 | #include "CodePatterns/Observer/Notification.hpp"
|
---|
24 |
|
---|
25 | /** This class wraps a value in another class and instance which we want to read
|
---|
26 | * from in a thread-safe manner.
|
---|
27 | *
|
---|
28 | * The information in this class is directly derived from information in another
|
---|
29 | * class that infrequently changes and whose changes are propagated through the
|
---|
30 | * Observer/Observable mechanism. I.e. it is very similar to the \sa Chacheable
|
---|
31 | * class, however here the information is updated directly. In other words, this
|
---|
32 | * class allows for disconnecting two classes from another when they depend
|
---|
33 | * oneway or bothways from information in the other class.
|
---|
34 | *
|
---|
35 | * A classical example is a GUI that needs to display internal data. Changes in
|
---|
36 | * the GUI occur too slowly, i.e. the class owning the value being displayed
|
---|
37 | * may be changed and destroyed before the GUI is actually updated. To safely
|
---|
38 | * access the value, the ObservedValue is put in between two class and the GUI.
|
---|
39 | * The value must be accessible via a getter and changes to the value must be
|
---|
40 | * propagated via update() or recieveNotification().
|
---|
41 | *
|
---|
42 | * In other words, the update is split up into two paths: a short path where
|
---|
43 | * just the required minimal information is updated (e.g. new position of the
|
---|
44 | * atom as \a NDIM \a Vector), and a long path where the result of the
|
---|
45 | * information update is created (e.g. the atom is redrawn). The short path
|
---|
46 | * must be short, i.e. require only few instructions, while the long path may
|
---|
47 | * contain many and may even be taken in a lazy fashion, i.e. regardless of
|
---|
48 | * the numnber of updates this path is only called every 1/20 second.
|
---|
49 | *
|
---|
50 | * The general building blocks are then as follows:
|
---|
51 | * -# an instance with an ObservedValue<T> \ foo as member variable
|
---|
52 | * -# for convenience the instance may construct an internal references to
|
---|
53 | * the entitiy that is observed for information update (and that is passed
|
---|
54 | * on to ObservedValue<T> cstor)
|
---|
55 | * -# static member function \a updateFoo() - this obtains an updated value used
|
---|
56 | * by \a foo upon receiving Observable's update() or recieveNotification()
|
---|
57 | * (e.g. requesting getPosition() from the respective \a atom). It may be
|
---|
58 | * static as it is independent of the internal state of the instance
|
---|
59 | * containing \a foo
|
---|
60 | * -# member function \a resetFoo() that uses ObservedValue<int>::get() to
|
---|
61 | * obtain a (thread)safe copy of the value containing all information to
|
---|
62 | * internally update the instance (e.g. redraw an atom's sphere at the new
|
---|
63 | * position)
|
---|
64 | * -# the instance containing \a foo is subscribed to the same channels and
|
---|
65 | * calls resetFoo() upon receiving the signal.
|
---|
66 | *
|
---|
67 | * For an example code see the respective unit test and the stub class used
|
---|
68 | * there.
|
---|
69 | */
|
---|
70 | template <typename T>
|
---|
71 | class ObservedValue : public Observer
|
---|
72 | {
|
---|
73 | public:
|
---|
74 | ObservedValue(
|
---|
75 | const Observable * const _owner,
|
---|
76 | const boost::function<T()> &_recalcMethod,
|
---|
77 | const std::string &_name,
|
---|
78 | const T &_initialvalue,
|
---|
79 | const Observable::channels_t &_channels);
|
---|
80 | ObservedValue(const ObservedValue &);
|
---|
81 | virtual ~ObservedValue();
|
---|
82 |
|
---|
83 | // methods implemented for base-class Observer
|
---|
84 | void update(Observable *publisher);
|
---|
85 | void recieveNotification(Observable *publisher, Notification_ptr notification);
|
---|
86 | virtual void subjectKilled(Observable *publisher);
|
---|
87 |
|
---|
88 | const T& get() const;
|
---|
89 |
|
---|
90 | private:
|
---|
91 | void activateObserver();
|
---|
92 | void deactivateObserver();
|
---|
93 |
|
---|
94 | private:
|
---|
95 | const Observable * const owner;
|
---|
96 | const boost::function<T()> recalcMethod;
|
---|
97 |
|
---|
98 | //!> contains list of possible channels to enlist, if empty we signOn globally
|
---|
99 | const Observable::channels_t channels;
|
---|
100 |
|
---|
101 | //!> whether we are still signed on or not
|
---|
102 | bool signedOn;
|
---|
103 |
|
---|
104 | //!> mutex to ensure access is only per-thread
|
---|
105 | mutable boost::recursive_mutex signedOnLock;
|
---|
106 |
|
---|
107 | //!> mutex to ensure access is only per-thread
|
---|
108 | mutable boost::recursive_mutex valueLock;
|
---|
109 |
|
---|
110 | //!> internal value associated with changes in Observable
|
---|
111 | T value;
|
---|
112 | };
|
---|
113 |
|
---|
114 | template <typename T>
|
---|
115 | ObservedValue<T>::ObservedValue(
|
---|
116 | const Observable * const _owner,
|
---|
117 | const boost::function<T()> &_recalcMethod,
|
---|
118 | const std::string &_name,
|
---|
119 | const T &_initialvalue,
|
---|
120 | const Observable::channels_t &_channels) :
|
---|
121 | Observer(_name + "(Cached)"),
|
---|
122 | owner(_owner),
|
---|
123 | recalcMethod(_recalcMethod),
|
---|
124 | channels(_channels),
|
---|
125 | signedOn(false),
|
---|
126 | value(_initialvalue)
|
---|
127 | {
|
---|
128 | // we sign on with the best(=lowest) priority, so cached values are recalculated before
|
---|
129 | // anybody else might ask for updated values
|
---|
130 | activateObserver();
|
---|
131 | }
|
---|
132 |
|
---|
133 | // de-activated copy constructor
|
---|
134 | template <typename T>
|
---|
135 | ObservedValue<T>::ObservedValue(const ObservedValue&)
|
---|
136 | {
|
---|
137 | ASSERT(0,"ObservedValues should never be copied");
|
---|
138 | }
|
---|
139 |
|
---|
140 | template <typename T>
|
---|
141 | ObservedValue<T>::~ObservedValue()
|
---|
142 | {
|
---|
143 | // make highest priorty such that ObservedValues are always updated first
|
---|
144 | deactivateObserver();
|
---|
145 | }
|
---|
146 |
|
---|
147 | template <typename T>
|
---|
148 | void ObservedValue<T>::activateObserver()
|
---|
149 | {
|
---|
150 | boost::lock_guard<boost::recursive_mutex> guard(signedOnLock);
|
---|
151 | if ((owner != NULL) && (!signedOn)) {
|
---|
152 | if (channels.empty()) {
|
---|
153 | owner->signOn(this,GlobalObservableInfo::PriorityLevel(int(-20)));
|
---|
154 | } else {
|
---|
155 | for (Observable::channels_t::const_iterator iter = channels.begin();
|
---|
156 | iter != channels.end(); ++iter)
|
---|
157 | owner->signOn(this,*iter);
|
---|
158 | }
|
---|
159 | signedOn = true;
|
---|
160 | }
|
---|
161 | }
|
---|
162 |
|
---|
163 | template <typename T>
|
---|
164 | void ObservedValue<T>::deactivateObserver()
|
---|
165 | {
|
---|
166 | boost::lock_guard<boost::recursive_mutex> guard(signedOnLock);
|
---|
167 | if ((owner != NULL) && (signedOn)) {
|
---|
168 | if (channels.empty()) {
|
---|
169 | owner->signOff(this);
|
---|
170 | } else {
|
---|
171 | for (Observable::channels_t::const_iterator iter = channels.begin();
|
---|
172 | iter != channels.end(); ++iter)
|
---|
173 | owner->signOff(this,*iter);
|
---|
174 | }
|
---|
175 | signedOn = false;
|
---|
176 | }
|
---|
177 | }
|
---|
178 |
|
---|
179 | template <typename T>
|
---|
180 | void ObservedValue<T>::update(Observable *publisher)
|
---|
181 | {
|
---|
182 | ASSERT( channels.empty(),
|
---|
183 | "ObservedValue<T>::update() - received general update although we are channel-centric.");
|
---|
184 | #ifdef LOG_OBSERVER
|
---|
185 | observerLog().addMessage() << "## ObservedValue " << observerLog().getName(this)
|
---|
186 | << " received global update.";
|
---|
187 | #endif
|
---|
188 | boost::lock_guard<boost::recursive_mutex> guard(valueLock);
|
---|
189 | value = recalcMethod();
|
---|
190 | }
|
---|
191 |
|
---|
192 | template <typename T>
|
---|
193 | void ObservedValue<T>::recieveNotification(Observable *publisher, Notification_ptr notification)
|
---|
194 | {
|
---|
195 | ASSERT( !channels.empty(),
|
---|
196 | "ObservedValue<T>::update() - received channel update although we are global.");
|
---|
197 |
|
---|
198 | if (publisher == owner) {
|
---|
199 | const Observable::channels_t::const_iterator iter =
|
---|
200 | std::find(channels.begin(), channels.end(), notification->getChannelNo());
|
---|
201 | if (iter != channels.end()) {
|
---|
202 | #ifdef LOG_OBSERVER
|
---|
203 | observerLog().addMessage() << "## ObservedValue " << observerLog().getName(this)
|
---|
204 | << " received local update in channel " << notification->getChannelNo() << ".";
|
---|
205 | #endif
|
---|
206 | boost::lock_guard<boost::recursive_mutex> guard(valueLock);
|
---|
207 | value = recalcMethod();
|
---|
208 | }
|
---|
209 | }
|
---|
210 | }
|
---|
211 |
|
---|
212 | template <typename T>
|
---|
213 | void ObservedValue<T>::subjectKilled(Observable *publisher)
|
---|
214 | {
|
---|
215 | boost::lock_guard<boost::recursive_mutex> guard(signedOnLock);
|
---|
216 | if (publisher == owner) {
|
---|
217 | signedOn = false;
|
---|
218 | }
|
---|
219 | }
|
---|
220 |
|
---|
221 | template <typename T>
|
---|
222 | const T& ObservedValue<T>::get() const
|
---|
223 | {
|
---|
224 | boost::lock_guard<boost::recursive_mutex> guard(valueLock);
|
---|
225 | return value;
|
---|
226 | }
|
---|
227 |
|
---|
228 | #endif /* OBSERVEDVALUE_HPP_ */
|
---|