Multithreaded FRC Robot Framework (Kinda Big)

I’m working on some code for an FRC Robot. I wanted to see if there’s any obvious improvements that can be made here, that stands out, or stuff I should avoid doing. This is a test bed for the base code, the actual robot code will consist mainly of several classes derived from these.

The program overall will run at 200 or 400 hz per thread to mitigate propagation delay from one thread to another. The actual code might be a bit too big to fit into here, but this is just meant to be a framework with some testing.

My only concern (other than the fact that this hasn’t been tested on the robot yet) at the moment is that I may want to use std::cout statements from the Master Thread, which can run at a much lower frequency (<20 hz). That will require accessing the thread-safe arrays of variables to retrieve and display the state of the robot. If this takes too long, it can hold the worker threads up. This can be found in MultiThreadDataArray::PrintData() and SafeMultithreadDataArray::PrintData().

Actual robot code is currently here: https://github.com/JakeAllison/RobotTestPC01

Here’s the file list that will be on the robot:

  • CheckedData.h / CheckedData.cpp
  • MultithreadDataArray.h / MultithreadDataArray.cpp
  • SafeMultithreadDataArray.h / SafeMultithreadDataArray.cpp
  • ThreadDataBase.h / ThreadDataBase.cpp
  • ThreadDataContainer.h / ThreadDataContainer.cpp
  • ThreadTaskBase.h / ThreadTaskBase.cpp

Below here are test files that represent the main thread, and any worker threads.

  • TestData.h / TestData.cpp
  • TestTask.h / TestTask.cpp
  • Test2Data.h / Test2Data.cpp
  • Test2Task.h / Test2Task.cpp
  • Bot_Codebase_Test.cpp (main)
  • Defines.h

This first class contains a single element of data. It has fields for validity, range checking, and a period of time before data becomes “stale” due to not being updated recently enough. These are critical for ensuring that the robot does not go crazy. If one of the control systems, sensors, or whatever stops working or gives bad data, then the robot can easily injure someone.

CheckedData.h:

/*  * CheckedData.h  *  *  Created on: Nov 8, 2018  *      Author: Jake  */  #ifndef SRC_UTILITIES_THREADSAFEDATA_CHECKEDDATA_H_ #define SRC_UTILITIES_THREADSAFEDATA_CHECKEDDATA_H_  #include <chrono> #include <Defines.h>  namespace frc { enum DataValidity {     DATA_VALIDITY_VALID,     DATA_VALIDITY_INVALID,     DATA_VALIDITY_DEGRADED,     DATA_VALIDITY_TEST };  template <typename T> class CheckedData { public:     CheckedData();     CheckedData(T initData = 0, T rangeMin = 0, T rangeMax = 0, unsigned int timeoutUS = 0);     virtual ~CheckedData();      bool SetRange(T rangeMin, T rangeMax);     bool SetTimeoutUS(unsigned int timeoutUS);     DataValidity SetData(T inputData, bool isTest = false, bool forceIfDegraded = false);     DataValidity GetData(T& outputData, bool forceIfTest = true, bool forceIfDegraded = true,                          bool forceIfInvalid = true); private:     const std::chrono::steady_clock::duration _zeroCompare;     std::chrono::steady_clock::time_point _updateDeadline;     std::chrono::steady_clock::duration _timeoutUS;      DataValidity _validity;     T _rangeMin, _rangeMax;     T _storedData; };  } /* namespace frc */  #include <Utilities/ThreadSafeData/CheckedData.cpp>  #endif /* SRC_UTILITIES_THREADSAFEDATA_CHECKEDDATA_H_ */ 

CheckedData.cpp

/*  * CheckedData.cpp  *  *  Created on: Nov 8, 2018  *      Author: Jake  */  #ifndef SRC_UTILITIES_THREADSAFEDATA_CHECKEDDATA_CPP_ #define SRC_UTILITIES_THREADSAFEDATA_CHECKEDDATA_CPP_  #include <Utilities/ThreadSafeData/CheckedData.h> namespace frc {  template <typename T> CheckedData<T>::CheckedData()         : _zeroCompare(std::chrono::microseconds(0))         , _updateDeadline(std::chrono::steady_clock::now())         , _timeoutUS(std::chrono::microseconds(0))         , _validity(DataValidity::DATA_VALIDITY_INVALID)         , _rangeMin(0)         , _rangeMax(0) , _storedData(0) {     // Nothing to do. }  template <typename T> CheckedData<T>::CheckedData(T initData, T rangeMin, T rangeMax, unsigned int timeoutUS)         : _zeroCompare(std::chrono::microseconds(0))         , _updateDeadline(std::chrono::steady_clock::now())         , _timeoutUS(std::chrono::microseconds(timeoutUS))         , _validity(DataValidity::DATA_VALIDITY_INVALID)         , _rangeMin(rangeMin)         , _rangeMax(rangeMax) , _storedData(initData) {     // Nothing here }  template <typename T> CheckedData<T>::~CheckedData() {     // Nothing here }  template <typename T> bool CheckedData<T>::SetRange(T rangeMin, T rangeMax) {     bool success = true;     if (rangeMax >= rangeMin) {         _rangeMax = rangeMax;         _rangeMin = rangeMin;     } else {         success = false;     }     return success; }  template <typename T> bool CheckedData<T>::SetTimeoutUS(unsigned int timeoutUS) {     _timeoutUS = std::chrono::microseconds(timeoutUS);     return true; }  template <typename T> DataValidity CheckedData<T>::SetData(T inputData, bool isTest, bool forceIfDegraded) {     DataValidity tempValid = DataValidity::DATA_VALIDITY_INVALID;      if (isTest) {         tempValid = DataValidity::DATA_VALIDITY_TEST;         _storedData = inputData;         _validity = tempValid;         _updateDeadline = _timeoutUS + std::chrono::steady_clock::now();     } else if ((_rangeMax > _rangeMin) && (inputData > _rangeMax || inputData < _rangeMin)) {         tempValid = DataValidity::DATA_VALIDITY_DEGRADED;         if (forceIfDegraded) {             _storedData = inputData;             _validity = tempValid;             _updateDeadline = _timeoutUS + std::chrono::steady_clock::now();         }     } else {         tempValid = DataValidity::DATA_VALIDITY_VALID;         _storedData = inputData;         _validity = tempValid;         _updateDeadline = _timeoutUS + std::chrono::steady_clock::now();     }     return tempValid; }  template <typename T> DataValidity CheckedData<T>::GetData(T& outputData, bool forceIfTest, bool forceIfDegraded, bool forceIfInvalid) {     if (_validity != DataValidity::DATA_VALIDITY_TEST && _timeoutUS > _zeroCompare) {         auto timestamp = std::chrono::steady_clock::now();         if (timestamp > _updateDeadline) {             _validity = DataValidity::DATA_VALIDITY_INVALID;         }     }      switch (_validity) {     case DataValidity::DATA_VALIDITY_VALID:         outputData = _storedData;         break;     case DataValidity::DATA_VALIDITY_INVALID:         if (forceIfInvalid) {             outputData = _storedData;         }         break;     case DataValidity::DATA_VALIDITY_DEGRADED:         if (forceIfDegraded) {             outputData = _storedData;         }         break;     case DataValidity::DATA_VALIDITY_TEST:         if (forceIfTest) {             outputData = _storedData;         }         break;     default:         // Do nothing         break;     }      return _validity; }  } /* namespace frc */      #endif /* SRC_UTILITIES_THREADSAFEDATA_CHECKEDDATA_CPP_ */ 

This class is designed to allow all threads to access an array of data using strings as keys. The purpose of this is to allow the developers of the threads to agree on variables such as “Gyro Angle”, “Motor Speed Demand”, “Turn Rate”, and so forth. This class houses regular data types that aren’t safety critical.

MultiThreadDataArray.h:

/*  * MultithreadDataArray.h  *  *  Created on: Nov 9, 2018  *      Author: Jake  */  #ifndef SRC_UTILITIES_THREADSAFEDATA_MULTITHREADDATAARRAY_H_ #define SRC_UTILITIES_THREADSAFEDATA_MULTITHREADDATAARRAY_H_  #include <unordered_map> #include <mutex> #include <string> #include <Defines.h>  namespace frc { template <typename T> class MultithreadDataArray { public:     MultithreadDataArray();     virtual ~MultithreadDataArray();     bool AddKey(std::string NewKey, T InitData = 0);     bool RemoveKey(std::string RemovedKey);     bool GetData(std::string DataKey, T& OutputData, std::string ContextMessage = "");     bool SetData(std::string DataKey, T InputData, std::string ContextMessage = "");      void PrintData(); #ifdef FOR_ROBOT      void SendToSmartDashboard(); #endif  private:     std::mutex _dataGuard;     std::unordered_map<std::string, T> _storedData; };  } /* namespace frc */  #include <Utilities/ThreadSafeData/MultithreadDataArray.cpp>  #endif /* SRC_UTILITIES_THREADSAFEDATA_MULTITHREADDATAARRAY_H_ */ 

MultithreadDataArray.cpp

/*  * MultithreadDataArray.cpp  *  *  Created on: Nov 9, 2018  *      Author: Jake  */  #ifndef SRC_UTILITIES_THREADSAFEDATA_MULTITHREADDATAARRAY_CPP_ #define SRC_UTILITIES_THREADSAFEDATA_MULTITHREADDATAARRAY_CPP_  #include <Utilities/ThreadSafeData/MultithreadDataArray.h> #include <iostream>  #ifdef FOR_ROBOT #include <SmartDashboard/SmartDashboard.h> #endif  namespace frc { template <typename T> MultithreadDataArray<T>::MultithreadDataArray() {     // TODO Auto-generated constructor stub }  template <typename T> MultithreadDataArray<T>::~MultithreadDataArray() {     // TODO Auto-generated destructor stub }  template <typename T> bool MultithreadDataArray<T>::AddKey(std::string NewKey, T InitData) {     bool success = true;     _dataGuard.lock();      bool insertSuccess = _storedData.insert(std::pair<std::string, T>(NewKey, InitData)).second;     if (!insertSuccess) {         success = false;     }      _dataGuard.unlock();     return success; }  template <typename T> bool MultithreadDataArray<T>::RemoveKey(std::string RemovedKey) {     bool success = false;     _dataGuard.lock();      bool keyErased = _storedData.erase(RemovedKey) != 0;     if (keyErased) {         success = true;     }      _dataGuard.unlock();     return success; }  template <typename T> bool MultithreadDataArray<T>::GetData(std::string DataKey, T& OutputData, std::string ContextMessage) {     bool success = true;     _dataGuard.lock();      bool keyExists = _storedData.find(DataKey) != _storedData.end();     if (keyExists) {         OutputData = _storedData.at(DataKey);     } else {         if (ContextMessage != "") {             std::cout << "Get() Key Failure: " << ContextMessage << ", " << DataKey << std::endl;         }         success = false;     }      _dataGuard.unlock();      return success; }  template <typename T> bool MultithreadDataArray<T>::SetData(std::string DataKey, T InputData, std::string ContextMessage) {     bool success = true;     _dataGuard.lock();      if (_storedData.find(DataKey) != _storedData.end()) {         _storedData[DataKey] = InputData;     } else {         if (ContextMessage != "") {             std::cout << "Set() Key Failure: " << ContextMessage << ", " << DataKey << ": " << InputData             << std::endl;         }         success = false;     }      _dataGuard.unlock();     return success; }  template <typename T> void MultithreadDataArray<T>::PrintData() {     _dataGuard.lock();      for (std::pair<std::string, T> element : _storedData) {         T temp = element.second;         std::cout << element.first << ": " << temp << std::endl;     }      _dataGuard.unlock(); }  #ifdef FOR_ROBOT  template <typename T> void MultithreadDataArray<T>::SendToSmartDashboard() {     for (std::pair<std::string, T> element : _storedData) {         T temp = element.second;         frc::SmartDashboard::PutNumber(element.first, static_cast<double>(temp));     } }  #endif  } /* namespace frc */  #endif 

Similar to the last, but made for CheckedData datatypes.

SafeMultiThreadDataArray.h:

/*  * SafeMultithreadDataArray.h  *  *  Created on: Nov 9, 2018  *      Author: Jake  */  #ifndef SRC_UTILITIES_THREADSAFEDATA_SAFEMULTITHREADDATAARRAY_H_ #define SRC_UTILITIES_THREADSAFEDATA_SAFEMULTITHREADDATAARRAY_H_  #include <unordered_map> #include <mutex> #include <string> #include <Defines.h> #include <Utilities/ThreadSafeData/CheckedData.h>  namespace frc {  template <typename T> class SafeMultithreadDataArray { public:     SafeMultithreadDataArray();     virtual ~SafeMultithreadDataArray();     bool AddKey(std::string NewKey, T InitData, T rangeMin = 0, T rangeMax = 0, unsigned int timeoutUS = 0);     bool RemoveKey(std::string RemovedKey);     bool GetData(std::string DataKey, T& OutputData, std::string ContextMessage = "", bool forceIfTest = false, bool forceIfDegraded = true, bool forceIfInvalid = false);     bool SetData(std::string DataKey, T InputData, std::string ContextMessage = "", bool isTest = false, bool forceIfDegraded = false);     void PrintData(); #ifdef FOR_ROBOT     void SendToSmartDashboard(); #endif   private:     std::mutex _dataGuard;     std::unordered_map<std::string, CheckedData<T>> _storedData; };  } /* namespace frc */  #include <Utilities/ThreadSafeData/SafeMultithreadDataArray.cpp>  #endif /* SRC_UTILITIES_THREADSAFEDATA_SAFEMULTITHREADDATAARRAY_H_ */ 

SafeMultiThreadedDataArray.cpp

/*  * SafeMultithreadDataArray.cpp  *  *  Created on: Nov 9, 2018  *      Author: Jake  */  #ifndef SRC_UTILITIES_THREADSAFEDATA_SAFEMULTITHREADDATAARRAY_CPP_ #define SRC_UTILITIES_THREADSAFEDATA_SAFEMULTITHREADDATAARRAY_CPP_  #include <Utilities/ThreadSafeData/SafeMultithreadDataArray.h> #include <iostream>  #ifdef FOR_ROBOT #include <SmartDashboard/SmartDashboard.h> #endif  namespace frc { template <typename T> SafeMultithreadDataArray<T>::SafeMultithreadDataArray() {     // TODO Auto-generated constructor stub }  template <typename T> SafeMultithreadDataArray<T>::~SafeMultithreadDataArray() {     // TODO Auto-generated destructor stub }  template <typename T> bool SafeMultithreadDataArray<T>::AddKey(std::string NewKey, T InitData, T rangeMin, T rangeMax, unsigned int timeoutUS) {     bool success = true;     _dataGuard.lock();     bool insertSuccess = _storedData.emplace(std::pair<std::string, CheckedData<T>>(NewKey, CheckedData<T>(InitData, rangeMin, rangeMax, timeoutUS))).second;     if (!insertSuccess) {         success = false;     }     _dataGuard.unlock();     return success; }  template <typename T> bool SafeMultithreadDataArray<T>::RemoveKey(std::string RemovedKey) {     bool success = false;     _dataGuard.lock();     bool keyErased = _storedData.erase(RemovedKey) != 0;     if (keyErased) {         success = true;     }     _dataGuard.unlock();     return success; }  template <typename T> bool SafeMultithreadDataArray<T>::GetData(std::string DataKey, T& OutputData, std::string ContextMessage, bool forceIfTest, bool forceIfDegraded,         bool forceIfInvalid) {     bool success = true;     _dataGuard.lock();     bool keyExists = _storedData.find(DataKey) != _storedData.end();     if (keyExists) {         DataValidity validity = _storedData.at(DataKey).GetData(OutputData, forceIfTest, forceIfDegraded, forceIfInvalid);         switch (validity) {         case DataValidity::DATA_VALIDITY_DEGRADED:             if(forceIfDegraded) {                 if (ContextMessage != "") {                     std::cout << "Get() Forced Degraded: " << ContextMessage << ", " << DataKey << std::endl;                 }             } else {                 if (ContextMessage != "") {                     std::cout << "Get() Degraded: " << ContextMessage << ", " << DataKey << std::endl;                 }                 success = false;             }             break;         case DataValidity::DATA_VALIDITY_INVALID:             if(forceIfDegraded) {                 if (ContextMessage != "") {                     std::cout << "Get() Forced Invalid: " << ContextMessage << ", " << DataKey << std::endl;                 }             } else {                 if (ContextMessage != "") {                     std::cout << "Get() Invalid: " << ContextMessage << ", " << DataKey << std::endl;                 }                 success = false;             }             break;         case DataValidity::DATA_VALIDITY_TEST:             if(forceIfDegraded) {                 if (ContextMessage != "") {                     std::cout << "Get() Forced Test: " << ContextMessage << ", " << DataKey << std::endl;                 }             } else {                 if (ContextMessage != "") {                     std::cout << "Get() Test: " << ContextMessage << ", " << DataKey << std::endl;                 }                 success = false;             }             break;         default:              break;         }      } else {         if (ContextMessage != "") {             std::cout << "Get() Key Failure: " << ContextMessage << ", " << DataKey << std::endl;         }         success = false;     }     _dataGuard.unlock();      return success; }  template <typename T> bool SafeMultithreadDataArray<T>::SetData(std::string DataKey, T InputData, std::string ContextMessage, bool isTest, bool forceIfDegraded) {     bool success = true;     _dataGuard.lock();     if (_storedData.find(DataKey) != _storedData.end()) {         DataValidity validity = _storedData.at(DataKey).SetData(InputData, isTest, forceIfDegraded);         switch (validity) {         case DataValidity::DATA_VALIDITY_DEGRADED:             if(forceIfDegraded) {                 if (ContextMessage != "") {                     std::cout << "Set() Forced Degraded: " << ContextMessage << ", " << DataKey << std::endl;                 }             } else {                 if (ContextMessage != "") {                     std::cout << "Set() Degraded: " << ContextMessage << ", " << DataKey << std::endl;                 }                 success = false;             }             break;         default:             // Nothing here             break;         }     } else {         if (ContextMessage != "") {             std::cout << "Set() Key Failure: " << ContextMessage << ", " << DataKey << ": " << InputData             << std::endl;         }         success = false;     }     _dataGuard.unlock();     return success; }  template <typename T> void SafeMultithreadDataArray<T>::PrintData() {     for (std::pair<std::string, CheckedData<T>> element : _storedData) {         T temp;         DataValidity validity = element.second.GetData(temp, true, true, true);         std::cout << element.first << ": " << validity << ", " << temp << std::endl;     } }  #ifdef FOR_ROBOT  template <typename T> void SafeMultithreadDataArray<T>::SendToSmartDashboard() {     for (std::pair<std::string, CheckedData<T>> element : _storedData) {         T temp;         DataValidity validity = element.second.GetData(temp, true, true, true);         frc::SmartDashboard::PutNumber(element.first + " Validity", static_cast<double>(validity));         frc::SmartDashboard::PutNumber(element.first, static_cast<double>(temp));     } }  #endif } /* namespace frc */  #endif 

This will contain all the data for a thread. Friends of the Derived classes from this will be able call the functions required to modify the data within this. Any thread will be able to use the Get() functions.

ThreadDataBase.h

/*  * ThreadDataBase.h  *  *  Created on: Oct 23, 2018  *      Author: Jake  */  #ifndef SRC_THREADDATABASE_H_ #define SRC_THREADDATABASE_H_   #include <Utilities/ThreadSafeData/MultithreadDataArray.h> #include <Utilities/ThreadSafeData/SafeMultithreadDataArray.h> #include <unordered_map> #include <mutex> #include <string> #include <condition_variable> #include <Defines.h>  namespace frc { class ThreadDataBase { public:     ThreadDataBase();     virtual ~ThreadDataBase();     template<typename T>     bool GetData(std::string DataKey, T& OutputData, std::string ContextMessage = "");     template<typename T>     bool GetSafeData(std::string DataKey, T& OutputData, std::string ContextMessage = "", bool forceIfTest = true, bool forceIfDegraded = true, bool forceIfInvalid = true);      void PrintData();  #ifdef FOR_ROBOT      void SendToSmartDashboard(); #endif     // void MakeAServiceRequest(bool Blocking, unsigned int MaxBlockTimeUS); protected:     template<typename T>     bool SetData(std::string DataKey, T InputData, std::string ContextMessage = "");     template<typename T>     bool SetSafeData(std::string DataKey, T InputData, std::string ContextMessage = "", bool isTest = false, bool forceIfDegraded = false);      template<typename T>     bool AddKey(std::string NewIndex, T initData = 0);     template<typename T>     bool AddSafeKey(std::string NewIndex, T initData = 0, T Min = 0, T Max = 0, unsigned int TimeoutUS = 0);      template<typename T>     bool RemoveKey(std::string RemovedKey);     template<typename T>     bool RemoveSafeKey(std::string RemovedKey);      std::mutex serviceGuard;     std::mutex mtxNotifier;     std::condition_variable conditionWait;  private:     MultithreadDataArray<int> _intData;     MultithreadDataArray<double> _doubleData;     SafeMultithreadDataArray<int> _intDataSafe;     SafeMultithreadDataArray<double> _doubleDataSafe;  };  } /* namespace frc */  #endif /* SRC_THREADDATABASE_H_ */ 

ThreadDataBase.cpp

/*  * ThreadDataBase.cpp  *  *  Created on: Oct 23, 2018  *      Author: Jake  */  #include <ThreadBases/ThreadDataBase.h> #include <iostream>  #ifdef FOR_ROBOT #include <SmartDashboard/SmartDashboard.h> #endif  namespace frc {  ThreadDataBase::ThreadDataBase() {     // TODO Auto-generated constructor stub }  ThreadDataBase::~ThreadDataBase() {     // TODO Auto-generated destructor stub }  /**  * Adds new key for type int.  * Returns true if the DataKey was successfully created.  * Returns false if the DataKey already exists.  * @param NewKey String that is used to identify the desired data.  * @param InitData Data being retrieved.  * @return success Returns success as true or false.  */ template<> bool ThreadDataBase::AddKey<int>(std::string NewKey, int initData) {     bool success = _intData.AddKey(NewKey, initData);     return success; }  /**  * Adds new key for type double.  * Returns true if the DataKey was successfully created.  * Returns false if the DataKey already exists.  * @param NewKey String that is used to identify the desired data.  * @param InitData Data being retrieved.  * @return success Returns success as true or false.  */ template<> bool ThreadDataBase::AddKey<double>(std::string NewKey, double initData) {     bool success = _doubleData.AddKey(NewKey, initData);     return success; }  /**  * Adds new key for type int.  * Returns true if the DataKey was successfully created.  * Returns false if the DataKey already exists.  * @param NewKey String that is used to identify the desired data.  * @param InitData Data being retrieved.  * @return success Returns success as true or false.  */ template<> bool ThreadDataBase::AddSafeKey<int>(     std::string NewKey, int initData, int Min, int Max, unsigned int TimeoutUS) {     bool success = _intDataSafe.AddKey(NewKey, initData, Min, Max, TimeoutUS);     return success; }  /**  * Adds new key for type double.  * Returns true if the DataKey was successfully created.  * Returns false if the DataKey already exists.  * @param NewKey String that is used to identify the desired data.  * @param InitData Data being retrieved.  * @return success Returns success as true or false.  */ template<> bool ThreadDataBase::AddSafeKey<double>(     std::string NewKey, double initData, double Min, double Max, unsigned int TimeoutUS) {     bool success = _doubleDataSafe.AddKey(NewKey, initData, Min, Max, TimeoutUS);     return success; }  /**  * Removes key for int data types.  * Returns true if the DataKey was successfully removed.  * Returns false if the DataKey doesn't exist.  * @param RemovedKey String that is used to identify the desired data.  * @return success Returns success as true or false.  */ template<> bool ThreadDataBase::RemoveKey<int>(std::string RemovedKey) {     bool success = _intData.RemoveKey(RemovedKey);     return success; }  /**  * Removes key for double data types.  * Returns true if the DataKey was successfully removed.  * Returns false if the DataKey doesn't exist.  * @param RemovedKey String that is used to identify the desired data.  * @return success Returns success as true or false.  */ template<> bool ThreadDataBase::RemoveKey<double>(std::string RemovedKey) {     bool success = _doubleData.RemoveKey(RemovedKey);     return success; }  /**  * Removes key for safe int data types.  * Returns true if the DataKey was successfully removed.  * Returns false if the DataKey doesn't exist.  * @param RemovedKey String that is used to identify the desired data.  * @return success Returns success as true or false.  */ template<> bool ThreadDataBase::RemoveSafeKey<int>(std::string RemovedKey) {     bool success = _intDataSafe.RemoveKey(RemovedKey);     return success; }  /**  * Removes key for safe double data types.  * Returns true if the DataKey was successfully removed.  * Returns false if the DataKey doesn't exist.  * @param RemovedKey String that is used to identify the desired data.  * @return success Returns success as true or false.  */ template<> bool ThreadDataBase::RemoveSafeKey<double>(std::string RemovedKey) {     bool success = _doubleDataSafe.RemoveKey(RemovedKey);     return success; }  /**  * Gets data of type int.  * Returns true if the DataKey exists for the output type.  * Returns false otherwise.  * @param DataKey String that is used to identify the desired data.  * @param OutputData Data being retrieved.  * @param ContextMessage Optional string for debugging.  * @return success Returns success as true or false.  */ template<> bool ThreadDataBase::GetData<int>(std::string DataKey, int& OutputData, std::string ContextMessage) {     bool success = _intData.GetData(DataKey, OutputData, ContextMessage);     return success; }  /**  * Gets data of type double.  * Returns true if the DataKey exists for the output type.  * Returns false otherwise.  * @param DataKey String that is used to identify the desired data.  * @param OutputData Data being retrieved.  * @param ContextMessage Optional string for debugging.  * @return success Returns success as true or false.  */ template<> bool ThreadDataBase::GetData<double>(std::string DataKey, double& OutputData, std::string ContextMessage) {     bool success = _doubleData.GetData(DataKey, OutputData, ContextMessage);     return success; }  /**  * Gets data of type int.  * Returns true if the DataKey exists for the output type.  * Returns false otherwise.  * @param DataKey String that is used to identify the desired data.  * @param OutputData Data being retrieved.  * @param ContextMessage Optional string for debugging.  * @return success Returns success as true or false.  */ template<> bool ThreadDataBase::GetSafeData<int>(std::string DataKey, int& OutputData, std::string ContextMessage, bool forceIfTest, bool forceIfDegraded, bool forceIfInvalid) {     bool success = _intDataSafe.GetData(DataKey, OutputData, ContextMessage, forceIfTest, forceIfDegraded, forceIfInvalid);     return success; }  /**  * Gets data of type double.  * Returns true if the DataKey exists for the output type.  * Returns false otherwise.  * @param DataKey String that is used to identify the desired data.  * @param OutputData Data being retrieved.  * @param ContextMessage Optional string for debugging.  * @return success Returns success as true or false.  */ template<> bool ThreadDataBase::GetSafeData<double>(std::string DataKey, double& OutputData, std::string ContextMessage, bool forceIfTest, bool forceIfDegraded, bool forceIfInvalid) {     bool success = _doubleDataSafe.GetData(DataKey, OutputData, ContextMessage, forceIfTest, forceIfDegraded, forceIfInvalid);     return success; }  /**  * Sets data of type int.  * Returns true if the DataKey exists for the output type.  * Returns false otherwise.  * @param DataKey String that is used to identify the desired data.  * @param Data being written.  * @param ContextMessage Optional string for debugging.  * @return success Returns success as true or false.  */ template<> bool ThreadDataBase::SetData<int>(std::string DataKey, int InputData, std::string ContextMessage) {     bool success = _intData.SetData(DataKey, InputData, ContextMessage);     return success; }  /**  * Sets data of type double.  * Returns true if the DataKey exists for the output type.  * Returns false otherwise.  * @param DataKey String that is used to identify the desired data.  * @param Data being written.  * @param ContextMessage Optional string for debugging.  * @return success Returns success as true or false.  */ template<> bool ThreadDataBase::SetData<double>(std::string DataKey, double InputData, std::string ContextMessage) {     bool success = _doubleData.SetData(DataKey, InputData, ContextMessage);     return success; }  /**  * Sets data of type int.  * Returns true if the DataKey exists for the output type.  * Returns false otherwise.  * @param DataKey String that is used to identify the desired data.  * @param Data being written.  * @param ContextMessage Optional string for debugging.  * @return success Returns success as true or false.  */ template<> bool ThreadDataBase::SetSafeData<int>(std::string DataKey, int InputData, std::string ContextMessage, bool isTest, bool forceIfDegraded) {     bool success = _intDataSafe.SetData(DataKey, InputData, ContextMessage, isTest, forceIfDegraded);     return success; }  /**  * Sets data of type double.  * Returns true if the DataKey exists for the output type.  * Returns false otherwise.  * @param DataKey String that is used to identify the desired data.  * @param Data being written.  * @param ContextMessage Optional string for debugging.  * @return success Returns success as true or false.  */ template<> bool ThreadDataBase::SetSafeData<double>(std::string DataKey, double InputData, std::string ContextMessage, bool isTest, bool forceIfDegraded) {     bool success = _doubleDataSafe.SetData(DataKey, InputData, ContextMessage, isTest, forceIfDegraded);     return success; }  void ThreadDataBase::PrintData() {     _intData.PrintData();     _doubleData.PrintData();     _intDataSafe.PrintData();     _doubleDataSafe.PrintData(); }  #ifdef FOR_ROBOT void ThreadDataBase::SendToSmartDashboard() {     _intData.PrintData();     _doubleData.PrintData();     _intDataSafe.PrintData();     _doubleDataSafe.PrintData(); } #endif } /* namespace frc */ 

This is simply a place to put the data for all the threads. Each thread will have it’s own data structure in here that only it can write to, but all the other threads can read from. Each thread will have a pointer to the actual instance of this class. The threads should be able to read and write with absolutely no regards for the activities of the other threads.

ThreadDataContainer.h

/*  * ThreadDataContainer.h  *  *  Created on: Nov 12, 2018  *      Author: Jake  */  #ifndef THREADBASES_THREADDATACONTAINER_H_ #define THREADBASES_THREADDATACONTAINER_H_  #include <Test/TestData.h> #include <Test2/Test2Data.h>  namespace frc {  class ThreadDataContainer { public:     ThreadDataContainer();     virtual ~ThreadDataContainer();      TestData _testData;     Test2Data _test2Data; };  } /* namespace frc */  #endif /* THREADBASES_THREADDATACONTAINER_H_ */ 

This is basically empty, but stuff can be added if needed.

ThreadDataContainer.cpp

/*  * ThreadDataContainer.cpp  *  *  Created on: Nov 12, 2018  *      Author: Jake  */  #include <ThreadBases/ThreadDataContainer.h>  namespace frc {  ThreadDataContainer::ThreadDataContainer() {     // TODO Auto-generated constructor stub  }  ThreadDataContainer::~ThreadDataContainer() {     // TODO Auto-generated destructor stub }  } /* namespace frc */ 

This is probably the most important part of the entire program. This is where the threads are created and controlled.

ThreadTaskBase.h

/*  * ThreadTaskBase.h  *  *  Created on: Oct 23, 2018  *      Author: Jake  */  #ifndef SRC_THREADTASKBASE_H_ #define SRC_THREADTASKBASE_H_  #include <thread> #include <mutex> #include <condition_variable> #include <Defines.h> #include <ThreadBases/ThreadDataContainer.h>  namespace frc {  class ThreadTaskBase { public:     ThreadTaskBase(ThreadDataContainer* threadData);     virtual ~ThreadTaskBase();     bool Start(const int periodUS);     void Stop(bool joinThread);     double GetPeriod(); protected:     ThreadDataContainer* _threadData; private:     bool _stopThread = false;     bool _taskOverloaded = false;     long int _periodUS = 10000;     std::thread _workerThread;     std::mutex _workerThreadGuard;      void ThreadMain(int periodUS);     virtual void ThreadTask();     // void ServiceRequestTemplate(); };  } /* namespace frc */  #endif /* SRC_THREADTASKBASE_H_ */ 

ThreadTaskBase.cpp

/*  * ThreadTaskBase.cpp  *  *  Created on: Oct 23, 2018  *      Author: Jake  */  #include <ThreadBases/ThreadTaskBase.h> #include <iostream>  #ifdef FOR_ROBOT #include <SmartDashboard/SmartDashboard.h> #endif  namespace frc {  /**  * Default constructor.  * Runs the default constructor for the worker thread, but doesn't  * actually start the thread.  */ ThreadTaskBase::ThreadTaskBase(ThreadDataContainer* threadData)         : _threadData(threadData), _workerThread() {     // Nothing to do here. }  /**  * Default destructor.  * Calls the Stop() function with the parameter TRUE to end the worker thread's  * execution and wait for it to finish.  */ ThreadTaskBase::~ThreadTaskBase() {     Stop(true); }  /**  * Default task that is executed periodically by ThreadMain().  * This is overridden by the user.  */ void ThreadTaskBase::ThreadTask() {     // Override this!!! };  /**  * Starts the worker thread which executes the main thread.  * Success if a worker thread was successfully created.  * Failure if a worker thread is already running, indicated by the mutex lock.  * @param periodUS The time period for processing cycles to occur in microseconds (US).  * @return Success Returns true if success, false if failure.  */ bool ThreadTaskBase::Start(int periodUS) {     if (_workerThreadGuard.try_lock()) {         _workerThread = std::thread(&ThreadTaskBase::ThreadMain, this, periodUS);         _workerThreadGuard.unlock();         return true;     } else {         return false;     } }  /**  * Sends the signal to stop thread execution.  * @param joinThread Whether or not to wait for the thread to finish execution.  */ void ThreadTaskBase::Stop(bool joinThread) {     _stopThread = true;     if (joinThread && _workerThread.joinable()) {         _workerThread.join();     } }  /**  * Returns the period of thread in seconds.  * @return Period Period between processing cycles.  */ double ThreadTaskBase::GetPeriod() {     return _periodUS; }  /**  * Main worker thread which executes ThreadTask() periodically.  * @param periodUS The time period for processing cycles to occur in microseconds (US).  */ void ThreadTaskBase::ThreadMain(const int periodUS) {     _periodUS = periodUS;     _stopThread = false;     _workerThreadGuard.lock();     while (!_stopThread) {         auto nextCycleTime = std::chrono::steady_clock::now() + std::chrono::microseconds(periodUS);         ThreadTask();         auto cycleEndTime = std::chrono::steady_clock::now();         if (cycleEndTime > nextCycleTime) {             _taskOverloaded = true;         } else {             _taskOverloaded = false;         }         std::this_thread::sleep_until(nextCycleTime);     }     _workerThreadGuard.unlock(); }  } /* namespace frc */ 

This is the data class for output data from TestTask class.

TestData.h

/*  * TestData.h  *  *  Created on: Nov 10, 2018  *      Author: Jake  */  #ifndef TEST_TESTDATA_H_ #define TEST_TESTDATA_H_  #include <ThreadBases/ThreadDataBase.h> #include <string>   namespace frc {  class TestData : public ThreadDataBase { public:     friend class TestTask;      TestData();     virtual ~TestData();     void SetDataManual(std::string datakey, int inputdata, std::string contextmessage = "") {         SetData(datakey, inputdata, contextmessage);     }     void SetDataManual(std::string datakey, double inputdata, std::string contextmessage = "") {         SetData(datakey, inputdata, contextmessage);     }     void SetSafeDataManual(std::string datakey, int inputdata, std::string contextmessage = "", bool isTest = false, bool forceIfDegraded = false) {         SetSafeData(datakey, inputdata, contextmessage, isTest, forceIfDegraded);     }     void SetSafeDataManual(std::string datakey, double inputdata, std::string contextmessage = "", bool isTest = false, bool forceIfDegraded = false) {         SetSafeData(datakey, inputdata, contextmessage, isTest, forceIfDegraded);     } };  } /* namespace frc */  #endif /* TEST_TESTDATA_H_ */ 

TestData.cpp

/*  * TestData.cpp  *  *  Created on: Nov 10, 2018  *      Author: Jake  */  #include <Test/TestData.h>  namespace frc {  TestData::TestData() {     // TODO Auto-generated constructor stub  }  TestData::~TestData() {     // TODO Auto-generated destructor stub }   } /* namespace frc */ 

This is the first bit of code that does actual work. It creates several keys during the constructor, then does periodic processing on the data.

TestTask.h

/*  * TestTaskData.h  *  *  Created on: Nov 10, 2018  *      Author: Jake  */  #ifndef TEST_TESTTASK_H_ #define TEST_TESTTASK_H_  #include <ThreadBases/ThreadTaskBase.h>  namespace frc {  class TestTask : public ThreadTaskBase { public:     TestTask(ThreadDataContainer* threadData);     virtual ~TestTask(); private:     void ThreadTask() override; };  } /* namespace frc */  #endif /* TEST_TESTTASK_H_ */ 

TestTask.cpp

/*  * TestTaskData.cpp  *  *  Created on: Nov 10, 2018  *      Author: Jake  */  #include <Test/TestTask.h> #include <iostream> namespace frc {  TestTask::TestTask(ThreadDataContainer* threadData): ThreadTaskBase(threadData) {     // ThreadCountKey demonstrates  the worker thread's ability to  update data.     _threadData->_testData.AddKey<int>("ThreadCountKey");      // UserCountKey demonstrates the ability of other threads to  update data.     _threadData->_testData.AddKey<int>("UserCountKey");       // Demonstration of data storage without range limitaions or minimum update periods.     _threadData->_testData.AddKey<int>("Key 1");     _threadData->_testData.AddKey<double>("Key 2");      // Demonstration of "Safe" data storage with no limits set.     _threadData->_testData.AddSafeKey<int>("Key 3");     _threadData->_testData.AddSafeKey<double>("Key 4");      // Demonstration of "Safe" data storage with:     // 1) Range checking. This will not update out-of-range values unless forced.     // 2) Data "Validity" requires that the data be updated every so often, or else the data is marked as stale, or "Invalid".     //     // These examples use ranges [-6, 6] and [-5, 5].     // The timeouts were set to 2,000,000 microseconds, or 2 seconds.     _threadData->_testData.AddSafeKey<int>("Key 5", 0, -6, 6, 2000000);     _threadData->_testData.AddSafeKey<double>("Key 6", 0, -5, 5, 2000000);      // This demonstrates the ability of both threads to share common data.     // Only the "owner" thread should be able to write to this and it is discouraged to have     //     multiple thread writing to the same data structure, but reading and writing can be done.     // That's why the "TestTask" class is a friend of "TestData".     _threadData->_testData.AddKey<int>("Shared Key"); }  TestTask::~TestTask() {     // TODO Auto-generated destructor stub }  void TestTask::ThreadTask() {      // This function reads the data, does some processing on the data, then writes the data back.     // This is done for both it's own data, and the shared data.      int temp1 = -0xFFFF;     _threadData->_testData.GetData("ThreadCountKey", temp1, "Thread Task");     temp1++;     _threadData->_testData.SetData("ThreadCountKey", temp1, "Thread Task");      int temp2 = -0xFFFF;     _threadData->_testData.GetData("Shared Key", temp2, "Thread Task");     temp2 += 1000;     _threadData->_testData.SetData("Shared Key", temp2, "Thread Task");      _threadData->_testData.PrintData(); }  } /* namespace frc */ 

Output data for 2nd worker thread.

Test2Data.h

/*  * Test2Data.h  *  *  Created on: Nov 11, 2018  *      Author: Jake  */  #ifndef TEST2_TEST2DATA_H_ #define TEST2_TEST2DATA_H_  #include <ThreadBases/ThreadDataBase.h>  namespace frc {  class Test2Data : public ThreadDataBase { public:     friend class Test2Task;     friend int main();     Test2Data();     virtual ~Test2Data(); };  } /* namespace frc */  #endif /* TEST2_TEST2DATA_H_ */ 

Test2Data.cpp

/*  * Test2Data.cpp  *  *  Created on: Nov 11, 2018  *      Author: Jake  */  #include <Test2/Test2Data.h>  namespace frc {  Test2Data::Test2Data() {     // TODO Auto-generated constructor stub  }  Test2Data::~Test2Data() {     // TODO Auto-generated destructor stub }   } /* namespace frc */ 

A 2nd worker thread to demonstrate multitasking abilities and the abilities of the threads to maintain consistent timing, as well as inter-thread communication.

Test2Task.h

/*  * Test2Task.h  *  *  Created on: Nov 11, 2018  *      Author: Jake  */  #ifndef TEST2_TEST2TASK_H_ #define TEST2_TEST2TASK_H_  #include <ThreadBases/ThreadTaskBase.h>  namespace frc {  class Test2Task : public ThreadTaskBase { public:     Test2Task(ThreadDataContainer* threadData);     virtual ~Test2Task(); private:     void ThreadTask() override; };  } /* namespace frc */  #endif /* TEST2_TEST2TASK_H_ */ 

Test2Task.cpp

/*  * Test2Task.cpp  *  *  Created on: Nov 11, 2018  *      Author: Jake  */  #include <Test2/Test2Task.h> #include <iostream>  namespace frc {  Test2Task::Test2Task(ThreadDataContainer* threadData): ThreadTaskBase(threadData) {     // TODO Auto-generated constructor stub  }  Test2Task::~Test2Task() {     // TODO Auto-generated destructor stub }   void Test2Task::ThreadTask() {      // This task reads one of the variables in the data from the other thread.     int temp = -0xFFFF;     _threadData->_testData.GetSafeData("Key 5", temp,"", true, true, true);     std::cout << "Thread 2: " << temp << std::endl;   }  } /* namespace frc */ 

Master thread for the whole program.

Bot_Codebase_Test.cpp

/*  * Bot_Codebase_Test.cpp.h  *  *  Created on: Nov 08, 2018  *      Author: Jake  */  #define WAIT_FOR_ENTER_PRESS std::cin.ignore();  #include <iostream> using namespace std; #include <Test/TestTask.h> #include <Test/TestData.h> #include <Test2/Test2Task.h>  int main() {     frc::ThreadDataContainer threadData;    // Contains data structures for each thread.     frc::TestTask testTask(&threadData);    // 1st worker thread.     frc::Test2Task test2Task(&threadData);  // 2nd worker thread.     testTask.Start(1000000);                // 1 hz     test2Task.Start(333333);                // 3 hz      int temp1 = 0;     double temp2 = 0;     int temp3 = 0;     double temp4 = 0;     int temp5 = 0;     double temp6 = 0;      while (true) {          WAIT_FOR_ENTER_PRESS          temp1 += 1;         temp2 += 0.333;         temp3 = ((temp3 < 10) ? (temp3 + 1) : -10);         temp4 = ((temp4 < 10.0) ? (temp4 + 0.5) : -10.0);         temp5 = ((temp5 < 10) ? (temp5 + 1) : -10);         temp6 = ((temp6 < 10.0) ? (temp6 + 0.4) : -10.0);          threadData._testData.SetDataManual("Key 1", temp1, "Input 1");         threadData._testData.SetDataManual("Key 2", temp2, "Input 2");         threadData._testData.SetSafeDataManual("Key 3", temp3, "Input 3", false, true);         threadData._testData.SetSafeDataManual("Key 4", temp4, "Input 4");         threadData._testData.SetSafeDataManual("Key 5", temp5, "Input 5");         threadData._testData.SetSafeDataManual("Key 6", temp6, "Input 6", true, false);          int temp2 = -0xFFFF;         threadData._testData.GetData("Shared Key", temp2, "Thread Task");         temp2 += 1;         threadData._testData.SetDataManual("Shared Key", temp2, "Thread Task");          int tempCount = -0xFFFF;         threadData._testData.GetData("UserCountKey", tempCount);         tempCount++;         threadData._testData.SetDataManual("UserCountKey", tempCount);         //Sleep(1500);     }     return 0; } 

Not really used at the moment.

Defines.h

/*  * Defines.h  *  *  Created on: Nov 9, 2018  *      Author: Jake  */  #ifndef SRC_DEFINES_H_ #define SRC_DEFINES_H_     #endif /* SRC_DEFINES_H_ */ 

SharePoint Framework/ issue/ drop down change event on web part/

I want to use drop down control on SharePoint framework webpart(done) and on change event to call some method/ some rest api and so.but it is not allowing.

I did not find any article where spfx web part is shown with drop down. whereever I found drop down, it was in web part edit mode.I am sure end user will laugh if I go and say hay edit web part, and choose drop down to change the data.

some body have achieved ?