mc_rtc  2.14.0
AsyncJob.h
Go to the documentation of this file.
1 /*
2  * Copyright 2025 CNRS-UM LIRMM, CNRS-AIST JRL
3  */
4 
5 #pragma once
6 #include <mc_rtc/clock.h>
8 #include <mc_rtc/log/Logger.h>
9 #include <future>
10 #include <optional>
11 
13 {
14 
87 template<typename Derived, typename Input, typename Result>
88 struct AsyncJob
89 {
90  struct Timers
91  {
92  double dt_startAsync = 0;
93  double dt_checkResult = 0;
94  double dt_loggerImpl = 0;
95  double dt_guiImpl = 0;
96 
97  // Async loggers
98  // Technically not guaranteed to be portable on all plateforms, but should be fine for the ones we care about
99  std::atomic<double> dt_compute{0};
100  };
101 
102  AsyncJob() = default;
103  AsyncJob(const AsyncJob &) = delete;
104  AsyncJob & operator=(const AsyncJob &) = delete;
105  AsyncJob(AsyncJob &&) = delete;
106  AsyncJob & operator=(AsyncJob &&) = delete;
107 
109  {
111  removeFromGUI();
112  }
113 
124  {
125  if(running_)
126  {
127  throw std::runtime_error("AsyncJob: cannot get write-access to the input while an async job is running. Please "
128  "ensure that running() is false before modifying the input in place");
129  }
130  return input_;
131  }
132 
133  void startAsync()
134  {
135  auto start_async = mc_rtc::clock::now();
136  futureResult_ = std::async(
137  [this]()
138  {
139  auto start_compute = mc_rtc::clock::now();
140  auto result = derived().computeJob();
142  return result;
143  });
144  running_ = true;
145  startedOnce_ = true;
146  canGetSharedState_ = true;
148  }
149 
150  bool running() const noexcept { return running_; }
151 
155  bool startedOnce() const noexcept { return startedOnce_; }
156 
170  bool checkResult()
171  {
172  if(!running_) return false;
173  auto start_check = mc_rtc::clock::now();
174  if(futureResult_.wait_for(std::chrono::seconds(0)) == std::future_status::ready && futureResult_.valid()
176  {
177 
178  try
179  {
180  lastResult_ = futureResult_.get();
181  }
182  catch(const std::exception & e)
183  {
184  mc_rtc::log::error("[{}] Exception in async job: {}", derived().name(), e.what());
185  running_ = false;
186  canGetSharedState_ = false;
187  return false;
188  }
189 
190  canGetSharedState_ = false; // prevent invalid multiple calls to future::get()
191  if(logger_ && !inLogger_)
192  {
193  timers_.dt_loggerImpl = mc_rtc::timedExecution([this]() { addToLogger_(); }).count();
194  inLogger_ = true;
195  }
196  else
197  {
199  }
200 
201  if(gui_ && !inGUI_)
202  {
203  timers_.dt_guiImpl = mc_rtc::timedExecution([this]() { addToGUI_(); }).count();
204  inGUI_ = true;
205  }
206  else
207  {
208  timers_.dt_guiImpl = 0;
209  }
210  running_ = false;
211  return true;
212  }
214  return false;
215  }
216 
217  const std::optional<Result> & lastResult() const noexcept { return lastResult_; }
218 
219  void addToLogger(mc_rtc::Logger & logger, const std::string & prefix)
220  {
221  if(logger_ && inLogger_) return;
222  logger_ = &logger;
223  loggerPrefix_ = prefix;
224  }
225 
226  void addToGUI(mc_rtc::gui::StateBuilder & gui, const std::vector<std::string> & category)
227  {
228  if(gui_ && inGUI_) return;
229  gui_ = &gui;
230  guiCategory_ = category;
231  }
232 
234  {
235  if(logger_)
236  {
237  logger_->removeLogEntries(this);
238  inLogger_ = false;
239  }
240  }
241 
243  {
244  if(gui_)
245  {
246  gui_->removeElements(this);
247  inGUI_ = false;
248  }
249  }
250 
251  std::string name() const { return "AsyncJob"; }
252 
253 protected: // bookkeeping for the async job
254  std::atomic<bool> running_ = false;
255  bool inLogger_ = false;
256  bool inGUI_ = false;
257  bool startedOnce_ = false;
258 
259  std::future<Result> futureResult_;
260  // true if futureResult_.get() has not yet been called, false otherwise
261  bool canGetSharedState_ = false;
262 
263  std::optional<Result> lastResult_;
265 
275 
276  // CRTP: derived must implement this
277  // Result computeJob();
278 
279  // CRTP: derived may implement these
280  void addToLoggerImpl() {}
281  void addToGUIImpl() {}
282 
283  // Store context for logging/GUI
284  mc_rtc::Logger * logger_ = nullptr;
285  std::string loggerPrefix_ = "";
287  std::vector<std::string> guiCategory_;
288 
289 protected:
290  Derived & derived() { return static_cast<Derived &>(*this); }
291 
293  {
294  auto contactPrefix = "perf_" + loggerPrefix_ + "_";
295  logger_->addLogEntry(contactPrefix + "startAsync [ms]", this, [this]() { return timers_.dt_startAsync; });
296  logger_->addLogEntry(contactPrefix + "checkResult [ms]", this, [this]() { return timers_.dt_checkResult; });
297  logger_->addLogEntry(contactPrefix + "loggerImpl [ms]", this, [this]() { return timers_.dt_loggerImpl; });
298  logger_->addLogEntry(contactPrefix + "guiImpl [ms]", this, [this]() { return timers_.dt_guiImpl; });
299  logger_->addLogEntry(contactPrefix + "async_compute [ms]", this, [this]() { return timers_.dt_compute.load(); });
300  derived().addToLoggerImpl();
301  }
302 
303  void addToGUI_() { derived().addToGUIImpl(); }
304 };
305 
306 // Helper alias for CRTP inheritance
307 template<typename Derived, typename Input, typename Result>
309 } // namespace mc_rtc::threading
auto Input(const std::string &name, T &value)
Definition: Input.h:18
void error(Args &&... args)
Definition: logging.h:90
Definition: AsyncJob.h:13
double elapsed_ms_count(std::chrono::time_point< mc_rtc::clock > start)
Definition: clock.h:50
Logs controller data to disk.
Definition: Logger.h:30
void removeLogEntries(const void *source)
void addLogEntry(const std::string &name, const SourceT *source, CallbackT &&get_fn, bool overwrite=false)
Definition: Logger.h:208
Definition: StateBuilder.h:28
void removeElements(void *source)
Definition: AsyncJob.h:91
double dt_startAsync
Definition: AsyncJob.h:92
std::atomic< double > dt_compute
Definition: AsyncJob.h:99
double dt_loggerImpl
Definition: AsyncJob.h:94
double dt_guiImpl
Definition: AsyncJob.h:95
double dt_checkResult
Definition: AsyncJob.h:93
Helper base class for asynchronous jobs using CRTP.
Definition: AsyncJob.h:89
Timers timers_
Definition: AsyncJob.h:264
bool startedOnce_
Definition: AsyncJob.h:257
bool inLogger_
Definition: AsyncJob.h:255
mc_rtc::Logger * logger_
Definition: AsyncJob.h:284
std::atomic< bool > running_
Definition: AsyncJob.h:254
bool inGUI_
Definition: AsyncJob.h:256
void addToGUIImpl()
Definition: AsyncJob.h:281
std::future< Result > futureResult_
Definition: AsyncJob.h:259
void addToGUI_()
Definition: AsyncJob.h:303
AsyncJob & operator=(const AsyncJob &)=delete
AsyncJob(const AsyncJob &)=delete
Derived & derived()
Definition: AsyncJob.h:290
bool startedOnce() const noexcept
Definition: AsyncJob.h:155
std::vector< std::string > guiCategory_
Definition: AsyncJob.h:287
bool checkResult()
Check if the asynchronous job has completed and handle bookkeeping.
Definition: AsyncJob.h:170
std::string name() const
Definition: AsyncJob.h:251
void addToLogger_()
Definition: AsyncJob.h:292
AsyncJob & operator=(AsyncJob &&)=delete
void addToLogger(mc_rtc::Logger &logger, const std::string &prefix)
Definition: AsyncJob.h:219
void addToLoggerImpl()
Definition: AsyncJob.h:280
const std::optional< Result > & lastResult() const noexcept
Definition: AsyncJob.h:217
bool running() const noexcept
Definition: AsyncJob.h:150
void removeFromGUI()
Definition: AsyncJob.h:242
void startAsync()
Definition: AsyncJob.h:133
void addToGUI(mc_rtc::gui::StateBuilder &gui, const std::vector< std::string > &category)
Definition: AsyncJob.h:226
~AsyncJob()
Definition: AsyncJob.h:108
std::string loggerPrefix_
Definition: AsyncJob.h:285
Input & input()
Access the input data for the job.
Definition: AsyncJob.h:123
std::optional< Result > lastResult_
Definition: AsyncJob.h:263
Input input_
Input data for the asynchronous job.
Definition: AsyncJob.h:274
mc_rtc::gui::StateBuilder * gui_
Definition: AsyncJob.h:286
AsyncJob(AsyncJob &&)=delete
bool canGetSharedState_
Definition: AsyncJob.h:261
void removeFromLogger()
Definition: AsyncJob.h:233