GCC Code Coverage Report


Directory: ./
File: include/na64calib/dispatcher.hh
Date: 2025-09-01 06:19:01
Exec Total Coverage
Lines: 76 91 83.5%
Functions: 43 59 72.9%
Branches: 48 57 84.2%

Line Branch Exec Source
1 /* This file is a part of NA64SW software.
2 * Copyright (C) 2015-2022 NA64 Collaboration, CERN
3 *
4 * NA64SW is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>. */
16
17 #pragma once
18
19 #include "na64util/str-fmt.hh"
20 #include "na64util/observer.hh"
21 #include "na64util/demangle.hh"
22
23 #include <log4cpp/Category.hh>
24 #include <typeindex>
25 #include <functional>
26 #include <cassert>
27
28 namespace na64dp {
29
30 namespace util {
31 std::string
32 calib_id_to_str( const std::pair<std::type_index, std::string> &
33 , bool forceNative=false );
34 } // namespace ::na64dp::util
35
36 namespace errors {
37
38 /// Thrown if no calibration found
39 class NoCalibrationInfoEntry : public std::runtime_error {
40 private:
41 const std::type_info & _ti;
42 const std::string _name;
43 public:
44 NoCalibrationInfoEntry( const std::string & nm
45 , const std::type_info & ti ) throw();
46 const std::string name() const { return _name; }
47 const std::type_info & type_info() const { return _ti; }
48 };
49
50 /// Raised when observable is not set yet, but handle dereferencing requested
51 class PrematureDereferencing : public std::runtime_error {
52 private:
53 const std::type_info & _ti;
54 const std::string _name;
55 public:
56 PrematureDereferencing( const std::string & nm
57 , const std::type_info & ti ) throw();
58 const std::string name() const { return _name; }
59 const std::type_info & type_info() const { return _ti; }
60 };
61
62 } // namespace ::na64dp::error
63
64 namespace calib {
65
66 /**\brief Container of calibration data observables
67 *
68 * Maintains runtime storages of the calibration data.
69 * Dispatches update calls when `set()` method is called among subscribed
70 * observers via the `Observable` interface.
71 *
72 * Helper methods to add and remove the calibration info consumers added --
73 * instances that subclassing the `Client` class to maintain (un-)binding
74 * on construction / destruction.
75 *
76 * \see Client
77 * */
78 class Dispatcher {
79 public:
80 /// Abstract base for calibration data entries (keeper objects)
81 28 struct BaseEntry { virtual ~BaseEntry(){} };
82 /// Calibration data type identifier
83 typedef std::pair<std::type_index, std::string> CIDataID;
84 /// A template method returning calibration data type identifier based on
85 /// type and name
86 template<typename T> static CIDataID
87 164 info_id( const std::string & nm ) {
88
1/1
✓ Branch 2 taken 114 times.
164 return std::make_pair( std::type_index(typeid(T)), nm );
89 }
90 protected:
91 /// Reference to logger
92 log4cpp::Category & _log;
93 /// Concrete type of calibration data providing implementing collection
94 /// entry for calibration data (keeper object)
95 template<typename T>
96 struct ConcreteEntry : public BaseEntry
97 , public util::Observable< T > {
98 T data; ///< a real data instance
99 19 ConcreteEntry() {} /// empty ctr (uninit data)
100 56 virtual ~ConcreteEntry() {}
101 ConcreteEntry( const T & data_ ) : data(data_) {}
102 //ConcreteEntry( T && data_ ) : data(data_) {}
103 };
104 /// Caliration data entries indexed by type ids
105 typedef std::unordered_map<CIDataID, BaseEntry *, util::PairHash> Index;
106 /// Indexes observables by their `std::type_index`
107 Index _ciDicts;
108 /// Finds the calibration data by type and name; throws
109 /// `NoCalibrationInfoEntry` on failure
110 template<typename T> Index::iterator
111 34 _find( const std::string & name ) {
112
2/2
✓ Branch 1 taken 23 times.
✓ Branch 4 taken 23 times.
34 auto it = _ciDicts.find( info_id<T>(name) );
113
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 22 times.
34 if( _ciDicts.end() == it ) {
114 2 throw errors::NoCalibrationInfoEntry( name, typeid(T) );
115 }
116 32 return it;
117 }
118 /// Finds the calibration data by type and name (const ver); throws
119 /// `NoCalibrationInfoEntry` on failure
120 template<typename T> Index::iterator
121 27 _find_or_create( const std::string & name ) {
122
2/2
✓ Branch 1 taken 19 times.
✓ Branch 4 taken 19 times.
27 auto it = _ciDicts.find( info_id<T>(name) );
123
2/2
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 5 times.
27 if( _ciDicts.end() == it ) {
124
1/1
✓ Branch 1 taken 14 times.
19 ConcreteEntry<T> * p = new ConcreteEntry<T>();
125
2/2
✓ Branch 1 taken 14 times.
✓ Branch 4 taken 14 times.
19 auto ir = _ciDicts.emplace( info_id<T>(name), p );
126
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
19 assert(ir.second);
127 19 it = ir.first;
128 19 _log.debug( "Created calibration info entry of type %s: %p"
129 " (upcasted to %p)"
130
1/1
✓ Branch 2 taken 14 times.
19 , util::calib_id_to_str(it->first).c_str()
131 , p
132 19 , it->second
133 );
134 }
135 27 return it;
136 }
137 public:
138 /// Default ctr, initializes logger
139 4 Dispatcher(log4cpp::Category & L) : _log(L) {}
140 /// Dtr, if not all the observables were wiped, prints warning
141 8 virtual ~Dispatcher() {
142
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
8 if( ! _ciDicts.empty() ) {
143 _log.warn( "%d calibration info observables remain on"
144 " dispatcher destruction; possible memory leak."
145 , _ciDicts.size() );
146 }
147 }
148 ///\brief Adds new calibration info client
149 ///
150 /// Optionally, introduces calibration observable if it does not exist
151 /// Returns `false` if observable was already added.
152 template<typename T> bool
153 27 subscribe( typename util::Observable<T>::iObserver & client
154 , const std::string & ciName ) {
155
1/1
✓ Branch 1 taken 19 times.
27 auto it = _find_or_create<T>( ciName );
156 27 auto entryPtr = static_cast<ConcreteEntry<T>*>(it->second);
157
1/1
✓ Branch 1 taken 19 times.
27 bool ir = entryPtr->bind_observer(client);
158
2/2
✓ Branch 1 taken 19 times.
✓ Branch 4 taken 19 times.
27 msg_debug( _log
159 , "Observer %p has been bound to"
160 " observable %p %s."
161 , &client, &(entryPtr->data)
162 , util::calib_id_to_str(info_id<T>(ciName)).c_str() );
163 27 return ir;
164 }
165 ///\brief Removes of calibation info client from subscription
166 ///
167 /// Optionally, removes calibration observable, if it was the last subscriber
168 template<typename T> void
169 25 unsubscribe( typename util::Observable<T>::iObserver & client
170 , const std::string & ciName ) {
171
1/1
✓ Branch 1 taken 18 times.
25 Index::iterator it;
172 try {
173
1/1
✓ Branch 1 taken 18 times.
25 it = _find<T>( ciName );
174 } catch(errors::NoCalibrationInfoEntry & e) {
175 _log.warn( "Unable to unbind observer %p"
176 " from non-existing calibration observable %s."
177 , &client
178 , util::calib_id_to_str(info_id<T>(ciName)).c_str() );
179 return;
180 }
181 25 auto entryPtr = static_cast<ConcreteEntry<T>*>(it->second);
182
1/1
✓ Branch 1 taken 18 times.
25 entryPtr->unbind_observer(client);
183
2/2
✓ Branch 1 taken 18 times.
✓ Branch 4 taken 18 times.
25 msg_debug( _log
184 , "Unbinding observer %p from"
185 " observable %p %s."
186 , &client, &(entryPtr->data)
187 , util::calib_id_to_str(info_id<T>(ciName)).c_str() );
188
3/3
✓ Branch 1 taken 18 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 14 times.
25 if( entryPtr->n_observers() ) {
189 // there are some subscribers, keep observable
190 6 return;
191 }
192
2/2
✓ Branch 1 taken 14 times.
✓ Branch 4 taken 14 times.
19 msg_debug( _log
193 , "Unbinding last observer from"
194 " observable %p %s causes deletion of observable."
195 , &(entryPtr->data)
196 , util::calib_id_to_str(info_id<T>(ciName)).c_str() );
197
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
19 delete entryPtr;
198
1/1
✓ Branch 1 taken 14 times.
19 _ciDicts.erase(it);
199 }
200
201 /**\brief Sets calibration data currently in use, notifying observables
202 *
203 * For non-existing observables, raises a `NoCalibrationInfoEntry`
204 * exception, so caller has to be sure that observable exists (check with
205 * `observable_exists()`)
206 * */
207 template<typename T> void
208 1 set( const std::string & name, const T & data ) {
209
1/1
✓ Branch 1 taken 1 times.
1 Index::iterator it;
210 //try {
211
1/1
✓ Branch 1 taken 1 times.
1 it = _find<T>(name);
212 //} catch(errors::NoCalibrationInfoEntry & e) {
213 // _log.warn(e.what());
214 // return;
215 //}
216
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 assert(it->second);
217 #if 0
218 auto ce = dynamic_cast<ConcreteEntry<T>*>(it->second);
219 if( !ce ) {
220 NA64DP_RUNTIME_ERROR( "Type integrity error while setting the data:"
221 " requested type is %s while C++ type of the entry is appeared to"
222 " be <%s;%s> (downcasted ptr is %p)."
223 , util::calib_id_to_str(info_id<T>(name)).c_str()
224 , util::demangle_cpp(info_id<T>(name).first.name()).get()
225 , name.c_str()
226 , it->second
227 );
228 }
229 ce->data = data;
230 #else
231 1 auto ce = static_cast<ConcreteEntry<T>*>(it->second);
232
1/1
✓ Branch 1 taken 1 times.
1 ce->data = data;
233 #endif
234
1/1
✓ Branch 1 taken 1 times.
1 ce->notify_observers( ce->data );
235
2/2
✓ Branch 1 taken 1 times.
✓ Branch 4 taken 1 times.
1 _log.debug( "Calibration data"
236 " observable of type %s has been updated (copy)."
237 , util::calib_id_to_str(info_id<T>(name)).c_str() );
238 1 }
239
240 template<typename T> void
241 8 set( const std::string & name, T && data ) {
242
1/1
✓ Branch 1 taken 4 times.
8 Index::iterator it;
243 //try {
244
1/1
✓ Branch 1 taken 3 times.
8 it = _find<T>(name);
245 //} catch(errors::NoCalibrationInfoEntry & e) {
246 // _log.warn(e.what());
247 // return;
248 //}
249
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
6 assert(it->second);
250 #if 0
251 auto ce = static_cast<ConcreteEntry<T>*>(it->second);
252 if( !ce ) {
253 NA64DP_RUNTIME_ERROR( "Type integrity error while setting the data:"
254 " requested type is %s while C++ type of the entryis appeared to"
255 " be <%s;%s> (downcasted ptr is %p)."
256 , util::calib_id_to_str(info_id<T>(name)).c_str()
257 , util::demangle_cpp(info_id<T>(name).first.name()).get()
258 , name.c_str()
259 , it->second
260 );
261 }
262 ce->data = std::move(data); // move?
263 #else
264 6 auto ce = static_cast<ConcreteEntry<T>*>(it->second);
265 6 ce->data = std::move(data);
266 #endif
267
1/1
✓ Branch 1 taken 3 times.
6 ce->notify_observers( ce->data );
268
2/2
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 3 times.
6 _log.debug( "Calibration data"
269 " observable of type %s has been updated (move by rvalue)."
270 , util::calib_id_to_str(info_id<T>(name)).c_str() );
271 6 }
272
273 /// Returns true if there is at least one item (subscriber)
274 7 bool observable_exists( const CIDataID & k ) const {
275
1/1
✓ Branch 1 taken 7 times.
7 return _ciDicts.end() != _ciDicts.find(k);
276 }
277
278 /// Shortcut to `has_subscribers()`
279 template<typename T>
280 bool has_subscribers(const std::string & name) const {
281 //return _ciDicts.end() != _ciDicts.find( info_id<T>(name) );
282 return observable_exists(info_id<T>(name));
283 }
284
285 #if 0 // xxx, we barely need it according to the design
286 /// Returns calibration data currently in use
287 template<typename T> const T &
288 get( const std::string & name ) const {
289 auto it = _find<T>(name);
290 auto ce = static_cast<ConcreteEntry<T>*>(it->second);
291 return ce->data;
292 }
293 #endif
294 };
295
296 struct CIDataIDCompare {
297 bool operator()( const Dispatcher::CIDataID & a
298 , const Dispatcher::CIDataID & b
299 ) const {
300 return a.first < b.first && std::hash<std::string>{}(a.second)
301 < std::hash<std::string>{}(b.second);
302 }
303 };
304
305
306 /**\brief Read-only reference to calibration data
307 *
308 * Instance of this class represents "cached" reference to the calibration data
309 * for cases when no sophisticated updating procedure is required. Note, that
310 * it's usecase is restricted to single dispatcher class as it's
311 * biding/unbinding mechanism is controlled by creation/destroying calls, so
312 * every instance of this class has to have its lifetime less than their
313 * dispatcher.
314 */
315 template<typename T>
316 class Handle : protected util::Observable<T>::iObserver {
317 private:
318 /// Dispatcher reference
319 Dispatcher & _d;
320 /// Subscription name
321 const std::string _name;
322 /// Info reference
323 const T * _dataPtr;
324 protected:
325 /// Updates referenced value; typically called by dispatcher.
326 ///
327 /// \note Note, that this method is usually overriden by subclasses. Make
328 /// sure that update is propagated to the `Handle` level to update
329 /// the associated reference if need.
330 3 virtual void handle_update( const T & newRef ) override {
331 3 _dataPtr = &newRef;
332 3 }
333 public:
334 /// Construct handle to particular calibration data info entry.
335 /// It remains invalid till next recaching.
336 24 Handle( const std::string & nm, Dispatcher & d ) : _d(d)
337 12 , _name(nm)
338 12 , _dataPtr(nullptr) {
339
1/1
✓ Branch 1 taken 12 times.
12 _d.subscribe<T>(*this, nm);
340 12 }
341 /// Copy ctr adds new subscriber
342 Handle( const Handle<T> & orig ) : _d(orig._d)
343 , _name(orig._name)
344 , _dataPtr(orig._dataPtr) {
345 _d.subscribe<T>(*this, _name);
346 }
347 /// rvalue-constructor prevents unsubscription
348 Handle( Handle<T> && orig ) : _d(orig.d)
349 , _name(orig.name)
350 , _dataPtr(orig.dataPtr) {
351 _d.subscribe<T>(*this, _name);
352 }
353 /// Destroy calibration data, unbinding the observer.
354 42 virtual ~Handle() {
355 24 _d.unsubscribe<T>(*this, _name);
356 42 }
357 /// Retrieve referenced value (dereferences data)
358 const T & get() const {
359 if( !_dataPtr ) {
360 throw errors::PrematureDereferencing( _name, typeid(T) );
361 }
362 return *_dataPtr;
363 }
364 /// Retrieve referenced value (dereferencing operator)
365 const T & operator*() const {
366 return this->get();
367 }
368 /// Returns referenced value as ptr
369 3 const T * operator->() const {
370
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if( !_dataPtr ) {
371 1 throw errors::PrematureDereferencing( _name, typeid(T) );
372 }
373 2 return _dataPtr;
374 }
375 /// Returns subscription name
376 const std::string & name_str() const { return _name; }
377 };
378
379 }
380 }
381
382