ChimeraTK-ApplicationCore 04.07.02
Loading...
Searching...
No Matches
UserInputValidator.h
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3#pragma once
4
5#include "AccessorConcepts.h"
6#include "ArrayAccessor.h"
7#include "ScalarAccessor.h"
8
9#include <boost/circular_buffer.hpp>
10#include <boost/fusion/container.hpp>
11
12namespace ChimeraTK {
13
14 // Forward-declarations
15 class Module;
16
93 template<typename... ACCESSORTYPES>
94 void add(
95 const std::string& errorMessage, const std::function<bool(void)>& isValidFunction, ACCESSORTYPES&... accessors);
96
101 template<std::ranges::input_range R>
103 void add(const std::string& errorMessage, const std::function<bool(void)>& isValidFunction, const R& accessors);
104
113 template<typename UserType, template<typename> typename Accessor>
114 void setFallback(Accessor<UserType>& accessor, UserType value);
115
116 template<typename UserType, template<typename> typename Accessor>
117 void setFallback(Accessor<UserType>& accessor, std::vector<UserType> value);
118
124 void setErrorFunction(const std::function<void(const std::string&)>& errorFunction);
125
145 bool validate(const ChimeraTK::TransferElementID& change);
146
156 bool validateAll();
157
163 public:
164 virtual ~AccessorHook() = default;
165
170 virtual void onAddValidator([[maybe_unused]] UserInputValidator& validator) {};
171
176 virtual void onReject() {};
177
182 virtual void onAccept() {};
183 };
184
185 protected:
186 static constexpr std::string_view tagValidatedVariable{"__UserInputValidator"};
187
188 // Helper function to set up queue lengths of valid values. Will be called automatically for the first call to
189 // validate
190 void finalise();
191
192 // Helper function for internal book keeping of accessors (prevent unnecessary overwrite of map entry, which might
193 // result in loss of fallback values).
194 template<typename UserType, template<typename> typename Accessor>
195 void addAccessorIfNeeded(Accessor<UserType>& accessor);
196
197 // Type-independent base class representing a variable passed at least once to add() or setFallback().
200 virtual ~VariableBase() = default;
201 virtual void reject(RejectionType type) = 0;
202 virtual void accept() = 0;
203 virtual void setHistorySize(size_t size) = 0;
204 };
205
206 // Type-dependent representation of all known variables.
207 template<typename UserType, template<typename> typename Accessor>
209 explicit Variable(Accessor<UserType>& accessor);
210 Variable() = delete;
211 ~Variable() override = default;
212
213 // called when validation function returned false
214 void reject(RejectionType type) override;
215
216 // called when validation function returned true
217 void accept() override;
218
219 void setHistorySize(size_t) override;
220
221 // value to revert to if reject() is called. Updated through accept().
222 boost::circular_buffer<std::vector<UserType>> lastAcceptedValue{};
223 std::vector<UserType> fallbackValue{UserType()};
224
225 // Reference to the accessor.
226 Accessor<UserType>& accessor;
227
228 std::size_t historyLength{1};
229 };
230
231 // Represents a validation condition
232 struct Validator {
233 explicit Validator(std::function<bool(void)> isValidTest, std::string errorMessage);
234 Validator() = delete;
235 Validator(const Validator& other) = default;
236
237 std::function<bool(void)> isValidFunction;
238 std::string errorMessage;
239 };
240
241 Validator* addValidator(const std::function<bool(void)>& isValidFunction, const std::string& errorMessage);
242
243 template<typename UserType, template<typename> typename Accessor>
244 void registerAccessorWithValidator(Accessor<UserType>& accessor, Validator* validator);
245
246 // List of Validator objects
247 std::list<Validator> _validators; // must not use std::vector as resizing it invalidates pointers to objects
248
249 // Map to find Variable object for given TransferElementID
250 std::map<ChimeraTK::TransferElementID, std::shared_ptr<VariableBase>> _variableMap;
251
252 // Map to find all Validators associated with the given TransferElementID
253 std::map<ChimeraTK::TransferElementID, std::vector<Validator*>> _validatorMap;
254
255 // Function to be called for reporting validation errors
256 std::function<void(const std::string&)> _errorFunction{
257 [](const std::string& m) { logger(Logger::Severity::warning, "UserInputValidator") << m; }};
258
259 std::unordered_set<ChimeraTK::TransferElementID> _downstreamInvalidatingReturnChannels;
262 bool _finalised{false};
263
265 };
266
267 /********************************************************************************************************************/
268
269 template<typename UserType, template<typename> typename Accessor>
270 void UserInputValidator::registerAccessorWithValidator(Accessor<UserType>& accessor, Validator* validator) {
271 addAccessorIfNeeded(accessor);
272 _validatorMap[accessor.getId()].push_back(validator);
273 }
274
275 /********************************************************************************************************************/
276
277 template<typename... ACCESSORTYPES>
279 const std::string& errorMessage, const std::function<bool(void)>& isValidFunction, ACCESSORTYPES&... accessors) {
280 boost::fusion::list<ACCESSORTYPES&...> accessorList{accessors...};
281 static_assert(boost::fusion::size(accessorList) > 0, "Must specify at least one accessor!");
282 assert(isValidFunction != nullptr);
283
284 auto* validator = addValidator(isValidFunction, errorMessage);
285
286 // create map of accessors to validators, also add accessors/variables to list
287 boost::fusion::for_each(accessorList, [&](auto& accessor) { registerAccessorWithValidator(accessor, validator); });
288 }
289
290 /********************************************************************************************************************/
291
292 template<std::ranges::input_range R>
295 const std::string& errorMessage, const std::function<bool(void)>& isValidFunction, const R& accessors) {
296 assert(isValidFunction != nullptr);
297
298 // create validator and store in list
299 _validators.emplace_back(isValidFunction, errorMessage);
300
301 // create map of accessors to validators, also add accessors/variables to list
302 for(auto& accessor : accessors) {
303 addAccessorIfNeeded(accessor);
304 _validatorMap[accessor.getId()].push_back(&_validators.back());
305 };
306 }
307
308 /********************************************************************************************************************/
309
310 template<typename UserType, template<typename> typename Accessor>
311 void UserInputValidator::setFallback(Accessor<UserType>& accessor, UserType value) {
312 addAccessorIfNeeded(accessor);
313 auto pv = std::dynamic_pointer_cast<Variable<UserType, Accessor>>(_variableMap.at(accessor.getId()));
314 assert(pv != nullptr);
315 if(pv->fallbackValue.size() != 1) {
316 throw ChimeraTK::logic_error(
317 "UserInputValidator::setFallback() with scalar value called for array-typed accessor '" + accessor.getName() +
318 "'.");
319 }
320 pv->fallbackValue[0] = value;
321 }
322
323 /********************************************************************************************************************/
324
325 template<typename UserType, template<typename> typename Accessor>
326 void UserInputValidator::setFallback(Accessor<UserType>& accessor, std::vector<UserType> value) {
327 addAccessorIfNeeded(accessor);
328 auto pv = std::dynamic_pointer_cast<Variable<UserType, Accessor>>(_variableMap.at(accessor.getId()));
329 assert(pv != nullptr);
330 if(pv->fallbackValue.size() != value.size()) {
331 throw ChimeraTK::logic_error(
332 "UserInputValidator::setFallback() with called with mismatching array length for accessor '" +
333 accessor.getName() + "'.");
334 }
335 pv->fallbackValue = value;
336 }
337 /********************************************************************************************************************/
338
339 template<typename UserType, template<typename> typename Accessor>
340 void UserInputValidator::addAccessorIfNeeded(Accessor<UserType>& accessor) {
341 if(_module == nullptr) {
342 _module = dynamic_cast<ApplicationModule*>(dynamic_cast<Module*>(accessor.getOwner())->findApplicationModule());
343 }
344 if(!_variableMap.count(accessor.getId())) {
346 // only add tag in initialisation phase, since later model modifications can lead to race conditions
347 accessor.addTag(std::string(tagValidatedVariable));
348 }
349 _variableMap[accessor.getId()] = std::make_shared<Variable<UserType, Accessor>>(accessor);
350
351 // Call the AccessorHook::onAddValidator() if present in the accessor
352 auto hook = boost::dynamic_pointer_cast<AccessorHook>(accessor.getImpl());
353 if(hook) {
354 hook->onAddValidator(*this);
355 }
356 }
357 }
358 /********************************************************************************************************************/
359
360 template<typename UserType, template<typename> typename Accessor>
362 : accessor(validatedAccessor) {
363 auto node = static_cast<VariableNetworkNode>(validatedAccessor);
364
365 if(node.getMode() != UpdateMode::push) {
366 throw ChimeraTK::logic_error("UserInputValidator can only be used with push-type inputs.");
367 }
368
369 if constexpr(std::derived_from<Accessor<UserType>, ChimeraTK::ScalarAccessor<UserType>>) {
370 fallbackValue.resize(1);
371 }
372 else {
373 fallbackValue.resize(accessor.getNElements());
374 }
375 lastAcceptedValue.set_capacity(1);
376 }
377
378 /********************************************************************************************************************/
379
380 template<typename UserType, template<typename> typename Accessor>
382 if(type == RejectionType::downstream && !lastAcceptedValue.empty()) {
383 lastAcceptedValue.pop_back();
384 }
385 if constexpr(std::derived_from<Accessor<UserType>, ChimeraTK::ScalarAccessor<UserType>>) {
386 if(lastAcceptedValue.empty()) {
387 accessor = fallbackValue[0];
388 }
389 else {
390 accessor = lastAcceptedValue.back()[0];
391 }
392 }
393 else {
394 if(lastAcceptedValue.empty()) {
395 accessor = fallbackValue;
396 }
397 else {
398 accessor = lastAcceptedValue.back();
399 }
400 }
401
402 // Call the AccessorHook::onReject() if present in the accessor
403 auto hook = boost::dynamic_pointer_cast<AccessorHook>(accessor.getImpl());
404 if(hook) {
405 hook->onReject();
406 }
407
408 if(accessor.isWriteable()) {
409 accessor.write();
410 }
411 }
412
413 /********************************************************************************************************************/
414 template<typename UserType, template<typename> typename Accessor>
416 if constexpr(std::derived_from<Accessor<UserType>, ChimeraTK::ScalarAccessor<UserType>>) {
417 auto savedValue = std::vector<UserType>(1);
418 savedValue[0] = accessor;
419 lastAcceptedValue.push_back(savedValue);
420 }
421 else {
422 auto savedValue = std::vector<UserType>(accessor.getNElements());
423 savedValue = accessor;
424 lastAcceptedValue.push_back(savedValue);
425 }
426
427 // Call the AccessorHook::onAccept() if present in the accessor
428 auto hook = boost::dynamic_pointer_cast<AccessorHook>(accessor.getImpl());
429 if(hook) {
430 hook->onAccept();
431 }
432 }
433 /********************************************************************************************************************/
434
435 template<typename UserType, template<typename> typename Accessor>
437 historyLength = 3 * size;
438 lastAcceptedValue.set_capacity(historyLength);
439 }
440
441 /********************************************************************************************************************/
442
443} // namespace ChimeraTK
LifeCycleState getLifeCycleState() const
Get the current LifeCycleState of the application.
static Application & getInstance()
Obtain instance of the application.
void addTag(const std::string &tag)
Add a tag to all Application-type nodes inside this group.
Base class for ApplicationModule and DeviceModule, to have a common interface for these module types.
Definition Module.h:21
Accessor for scalar variables (i.e.
Accessors inheriting from this class (in addition to their accessor base class) can get informed abou...
virtual void onReject()
Called when UserInputValidator::validate() (or validateAll()) rejects an incoming or initial value.
virtual void onAddValidator(UserInputValidator &validator)
Called when the accessor is added to the validator, i.e.
virtual void onAccept()
Called when UserInputValidator::validate() (or validateAll()) accepts an incoming or initial value,...
Class describing a node of a variable network.
Concept requiring a type to be one of the ApplicationCore push input accessor types (scalar or array)...
InvalidityTracer application module.
@ initialisation
Initialisation phase including ApplicationModule::prepare().
Logger::StreamProxy logger(Logger::Severity severity, std::string context)
Convenience function to obtain the logger stream.
Definition Logger.h:124
Validator(const Validator &other)=default
virtual void reject(RejectionType type)=0
virtual void setHistorySize(size_t size)=0
void reject(RejectionType type) override
boost::circular_buffer< std::vector< UserType > > lastAcceptedValue
Class to realise the validation of user input values.
Validator * addValidator(const std::function< bool(void)> &isValidFunction, const std::string &errorMessage)
bool validate(const ChimeraTK::TransferElementID &change)
Execute all validations for the given change.
void setErrorFunction(const std::function< void(const std::string &)> &errorFunction)
Define how to report error messages to the user.
static constexpr std::string_view tagValidatedVariable
std::list< Validator > _validators
void addAccessorIfNeeded(Accessor< UserType > &accessor)
std::unordered_set< ChimeraTK::TransferElementID > _downstreamInvalidatingReturnChannels
std::map< ChimeraTK::TransferElementID, std::shared_ptr< VariableBase > > _variableMap
std::map< ChimeraTK::TransferElementID, std::vector< Validator * > > _validatorMap
void registerAccessorWithValidator(Accessor< UserType > &accessor, Validator *validator)
void add(const std::string &errorMessage, const std::function< bool(void)> &isValidFunction, ACCESSORTYPES &... accessors)
Add new condition to validate the given accessors against.
std::function< void(const std::string &)> _errorFunction
void setFallback(Accessor< UserType > &accessor, UserType value)
Provide fallback value for the given accessor.
bool validateAll()
Evaluate all validation conditions and correct all invalid values.