| 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 |