All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
ChangeMonitor.h
Go to the documentation of this file.
1 /**
2  * @file icarusalg/Utilities/ChangeMonitor.h
3  * @brief Classes to detect the change of object values.
4  * @author Gianluca Petrillo (petrillo@slac.stanford.edu)
5  * @date September 16, 2020
6  */
7 
8 #ifndef ICARUSALG_UTILITIES_CHANGEMONITOR_H
9 #define ICARUSALG_UTILITIES_CHANGEMONITOR_H
10 
11 
12 // C/C++ standard libraries
13 #include <mutex>
14 #include <optional>
15 #include <utility> // std::move()
16 #include <functional> // std::equal_to<>
17 
18 
19 namespace icarus::ns::util {
20 
21  //----------------------------------------------------------------------------
22  /**
23  * @brief Helper to check if an object has changed.
24  * @tparam T type of the object
25  * @tparam Comp type of the comparison between `T` objects
26  *
27  * This class can report if a value has changed from a previous check.
28  * The usage pattern is:
29  * 1. a `ChangeMonitor` is created
30  * 2. the monitor object is given a value, which becomes the new reference
31  * 3. arbitrary processing happens
32  * 4. the monitor object is given another value, which becomes the new
33  * reference; if this value is different from the previous reference, the
34  * previous reference is returned
35  * 5. steps 2 and 3 can be reiterated
36  *
37  * Example of usage:
38  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
39  * // starts with no reference by default
40  * icarus::ns::util::ChangeMonitor<int> monitor;
41  *
42  * // first check just establishes the reference
43  * int var = 0;
44  * monitor(var); // this is also a `update()`, which returns no value
45  *
46  * // reference is 0, new value is 1: a change is detected
47  * if (monitor(1)) {
48  * std::cout << "Value has changed!" << std::endl;
49  * }
50  *
51  * var = 5; // this does not change the monitoring
52  * // reference is now 1, new value is 1: no change is detected
53  * if (monitor(1)) {
54  * std::cout << "Value has changed again!" << std::endl;
55  * }
56  *
57  * // more complex syntax (C++17) allows accessing the old reference value;
58  * // reference is now 1, new value is 2: change is detected
59  * if (auto prevVal = monitor(2); prevVal) {
60  * std::cout << "Value has changed from " << *prevVal << " to 2!"
61  * << std::endl;
62  * }
63  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
64  * A few observations:
65  * * the object with default construction starts with no reference value;
66  * since changes are reported only when a reference value is available,
67  * the first `update()` will not report any change; a constructor is
68  * available to establish a reference at construction time;
69  * * the monitor operates on _values_ but it does not monitor a variable the
70  * value might be stored in; therefore, even if the value of the variable
71  * `var` which established the reference is later changed, the monitor is
72  * oblivious to that change.
73  *
74  * A copy of the "current" `T` object is kept in this object.
75  *
76  * Requirements for `T`:
77  *
78  * * must be _CopyConstructible_
79  * * must be _EqualityComparable_ (if `Comp` is default)
80  *
81  * @note This implementation is not thread-safe. For a thread-safe
82  * change monitor, see `icarus::ns::util::ThreadSafeChangeMonitor`.
83  */
84  template <typename T, typename Comp = std::equal_to<T>>
85  struct ChangeMonitor {
86 
87  using Data_t = T; ///< Type of the object being monitored.
88  using Comparison_t = Comp; ///< Type of object for reference comparison.
89 
90  /// Default constructor: starts with no reference value.
91  ChangeMonitor(Comparison_t comp = Comparison_t{}): fComp(std::move(comp)) {}
92 
93  /// Constructor: starts with `ref` as the reference value.
94  ChangeMonitor(Data_t const& ref, Comp comp = Comp{})
95  : fRefObj(ref), fComp(std::move(comp)) {}
96 
97 
98  /**
99  * @brief Returns the old object if different from `newObj`.
100  * @param currentObj the current object value
101  * @return the old reference if different from `currentObj`, or no value
102  *
103  * If there is no reference value, no value is returned.
104  * Otherwise, the reference is updated and the value of the old reference
105  * is returned.
106  * After each call, the reference value will be equivalent to `currentObj`.
107  *
108  * @note "No value returned" technically means that the `std::optional`
109  * object returned has no value, i.e. `has_value()` is `false`.
110  */
111  std::optional<Data_t> update(Data_t const& currentObj)
112  {
113  if (fRefObj && same(currentObj, fRefObj.value())) return {};
114  auto lastObj = std::move(fRefObj);
115  fRefObj.emplace(currentObj);
116  return lastObj;
117  }
118 
119  /// As `update()`.
120  std::optional<Data_t> operator() (Data_t const& currentObj)
121  { return update(currentObj); }
122 
123  /// Returns whether a reference value is present.
124  bool hasReference() const { return fRefObj.has_value(); }
125 
126  /// Returns the reference value; undefined if `hasReference()` is `false`.
127  Data_t const& reference() const { return fRefObj.value(); }
128 
129  private:
130 
131  /// The last object seen.
132  std::optional<Data_t> fRefObj;
133 
134  Comparison_t fComp; ///< Comparison used for reference testing.
135 
136  /// Returns whether `A` and `B` represent the same value.
137  bool same(Data_t const& A, Data_t const& B) const { return fComp(A, B); }
138 
139  }; // ChangeMonitor
140 
141 
142  // Deduction guide: a single parameter is always a reference value.
143  template <typename T>
144  ChangeMonitor(T const&) -> ChangeMonitor<T>;
145 
146 
147  // ---------------------------------------------------------------------------
148  /**
149  * @brief Helper to check if an object has changed. Thread-safe.
150  * @tparam T type of the object
151  * @tparam Comp type of the comparison between `T` objects
152  *
153  * This class operates like `ChangeMonitor`, but it is made thread-safe by
154  * the use of a mutex.
155  *
156  * @note This class is actually only _partially_ thread-safe:
157  * the member `reference()` is effectively not, since it returns a
158  * reference that can be then modified by another thread while accessed
159  * (read only) by another.
160  */
161  template <typename T, typename Comp = std::equal_to<T>>
162  class ThreadSafeChangeMonitor: public ChangeMonitor<T, Comp> {
163 
165  using Data_t = typename Base_t::Data_t;
166 
167  mutable std::mutex fLock;
168 
169  public:
170 
171  // inherit constructors too
173 
174 
175  /**
176  * @brief Returns the old object if different from `newObj`.
177  * @param currentObj the current object value
178  * @return the old reference if different from `currentObj`, or no value
179  * @see `ChangeMonitor::update()`
180  *
181  * See `ChangeMonitor::update()` for details.
182  */
183  std::optional<Data_t> update(Data_t const& currentObj)
184  { std::lock_guard lg { fLock }; return Base_t::update(currentObj); }
185 
186  /// As `update()`.
187  std::optional<Data_t> operator() (Data_t const& currentObj)
188  { return update(currentObj); }
189 
190  /// Returns whether a reference value is present.
191  bool hasReference() const
192  { std::lock_guard lg { fLock }; return Base_t::hasReference(); }
193 
194  /// Returns the reference value; undefined if `hasReference()` is `false`.
195  /// @note While the method is thread-safe, it returns a reference to a
196  /// mutable object.
197  Data_t const& reference() const
198  { std::lock_guard lg { fLock }; return Base_t::reference(); }
199 
200  }; // ThreadSafeChangeMonitor
201 
202 
203  // Deduction guide: a single parameter is always a reference value.
204  template <typename T>
205  ThreadSafeChangeMonitor(T const&) -> ThreadSafeChangeMonitor<T>;
206 
207  // ---------------------------------------------------------------------------
208 
209 } // namespace icarus::ns::util
210 
211 
212 #endif // ICARUSALG_UTILITIES_CHANGEMONITOR_H
Comparison_t fComp
Comparison used for reference testing.
ChangeMonitor(Comparison_t comp=Comparison_t{})
Default constructor: starts with no reference value.
Definition: ChangeMonitor.h:91
bool same(Data_t const &A, Data_t const &B) const
Returns whether A and B represent the same value.
bool hasReference() const
Returns whether a reference value is present.
ChangeMonitor(Data_t const &ref, Comp comp=Comp{})
Constructor: starts with ref as the reference value.
Definition: ChangeMonitor.h:94
Helper to check if an object has changed. Thread-safe.
std::optional< Data_t > operator()(Data_t const &currentObj)
As update().
ChangeMonitor(T const &) -> ChangeMonitor< T >
bool hasReference() const
Returns whether a reference value is present.
std::optional< Data_t > operator()(Data_t const &currentObj)
As update().
std::equal_to< icarus::trigger::icarus::trigger::BeamGateStruct > Comparison_t
Type of object for reference comparison.
Definition: ChangeMonitor.h:88
std::optional< Data_t > update(Data_t const &currentObj)
Returns the old object if different from newObj.
std::optional< Data_t > fRefObj
The last object seen.
std::optional< Data_t > update(Data_t const &currentObj)
Returns the old object if different from newObj.
Helper to check if an object has changed.
Definition: ChangeMonitor.h:85
float A
Definition: dedx.py:137
ThreadSafeChangeMonitor(T const &) -> ThreadSafeChangeMonitor< T >
Data_t const & reference() const
Returns the reference value; undefined if hasReference() is false.