baseexperiment.cpp
1 /********************************************************************************
2  * FARSA *
3  * Copyright (C) 2007-2012 *
4  * Gianluca Massera <emmegian@yahoo.it> *
5  * Stefano Nolfi <stefano.nolfi@istc.cnr.it> *
6  * Tomassino Ferrauto <tomassino.ferrauto@istc.cnr.it> *
7  * *
8  * This program is free software; you can redistribute it and/or modify *
9  * it under the terms of the GNU General Public License as published by *
10  * the Free Software Foundation; either version 2 of the License, or *
11  * (at your option) any later version. *
12  * *
13  * This program is distributed in the hope that it will be useful, *
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16  * GNU General Public License for more details. *
17  * *
18  * You should have received a copy of the GNU General Public License *
19  * along with this program; if not, write to the Free Software *
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *
21  ********************************************************************************/
22 
23 #include "baseexperiment.h"
24 #include "baseexperimentgui.h"
25 
26 namespace farsa {
27 
28 namespace __BaseExperiment_internal {
37  {
38  public:
44  m_experiment(experiment)
45  {
46  }
47 
52  {
53  // Nothing to do here
54  }
55 
62  virtual void fillActionsMenu(QMenu* actionsMenu)
63  {
64  m_experiment->fillActionsMenu(actionsMenu);
65  }
66 
77  virtual QList<ParameterSettableUIViewer> getViewers(QWidget* parent, Qt::WindowFlags flags)
78  {
79  return m_experiment->getViewers(parent, flags);
80  }
81 
88  virtual void addAdditionalMenus(QMenuBar* menuBar)
89  {
90  m_experiment->addAdditionalMenus(menuBar);
91  }
92 
93  private:
97  BaseExperiment* const m_experiment;
98  };
99 }
100 
101 BaseExperiment::Notifee::Notifee(BaseExperiment& experiment) :
102  NewDatumNotifiable<__BaseExperiment_internal::OperationControl>(),
103  m_experiment(experiment)
104 {
105 }
106 
107 BaseExperiment::Notifee::~Notifee()
108 {
109 }
110 
111 void BaseExperiment::Notifee::newDatumAvailable(DataDownloader<__BaseExperiment_internal::OperationControl>* downloader)
112 {
113  const __BaseExperiment_internal::OperationControl* d = downloader->downloadDatum();
114 
115  switch (d->action) {
117  m_experiment.runOperation(d->operationID);
118  break;
120  m_experiment.pause();
121  m_experiment.runOperation(d->operationID);
122  break;
124  m_experiment.stop();
125  break;
127  m_experiment.pause();
128  break;
130  m_experiment.step();
131  break;
133  m_experiment.resume();
134  break;
136  m_experiment.changeInterval(d->interval, false);
137  break;
138  }
139 }
140 
142  Component(),
144  ThreadOperation(),
145  m_operationsVector(),
146  m_actionSignalsMapper(new QSignalMapper(NULL)), // parent is NULL because we take care of deleting this object by ourself
147  m_workerThread(new WorkerThread(NULL)), // parent is NULL because we take care of deleting this object by ourself
148  m_runningOperationID(-1),
149  m_batchRunning(false),
150  m_stop(false),
151  m_mutex(),
152  m_waitCondition(),
153  m_pause(false),
154  m_previousPauseStatus(false),
155  m_delay(0),
156  m_notifee(*this),
157  m_dataExchange(2, DataUploaderDownloader<__BaseExperiment_internal::OperationStatus, __BaseExperiment_internal::OperationControl>::OverrideOlder, &m_notifee, NULL)
158 {
159  // Disabling the check of association before upload because we could never get associated (e.g. when running in batch)
160  m_dataExchange.checkAssociationBeforeUpload(false);
161 
162  // We have to add the stop() operation
163  addOperation("stopCurrentOperation", &BaseExperiment::stopCurrentOperation, false);
164 
165  // Connecting the mapped signal of m_actionSignalsMapper to our slot
166  connect(m_actionSignalsMapper.get(), SIGNAL(mapped(int)), this, SLOT(runOperation(int)));
167  connect(m_workerThread.get(), SIGNAL(exceptionDuringOperation(farsa::BaseException*)), this, SLOT(exceptionDuringOperation(farsa::BaseException*)), Qt::BlockingQueuedConnection);
168 }
169 
171 {
172  // Stopping the thread
173  m_workerThread->quit();
174 
175  // Removing all operation wrappers
176  foreach (AbstractOperationWrapper* op, m_operationsVector) {
177  delete op;
178  }
179 }
180 
182 {
183  // Reading whether we are running in batch mode or not. The parameter is only present if we are running in batch,
184  // so we must use false as default value for m_batchRunning
185  m_batchRunning = ConfigurationHelper::getBool(params, "__INTERNAL__/BatchRunning", false);
186 }
187 
188 void BaseExperiment::save(ConfigurationParameters& params, QString prefix)
189 {
190  // This should always be called
191  params.startObjectParameters(prefix, "BaseExperiment", this);
192 }
193 
194 void BaseExperiment::describe(QString type)
195 {
196  Descriptor d = addTypeDescription(type, "The base class of experiments");
197 }
198 
200 {
201  // Starting the inner thread
202  m_workerThread->start();
203 
204  setStatus("Configured");
205 }
206 
208 {
210 }
211 
212 void BaseExperiment::fillActionsMenu(QMenu* actionsMenu)
213 {
214  // This simply gets the list of actions from getActionsForOperations() and then adds them to the menu
215  QList<QAction*> actions = getActionsForOperations(actionsMenu);
216 
217  foreach (QAction* a, actions) {
218  actionsMenu->addAction(a);
219  }
220 }
221 
222 QList<ParameterSettableUIViewer> BaseExperiment::getViewers(QWidget* parent, Qt::WindowFlags flags)
223 {
224  // The default implementation only adds the BaseExperimentGUI widget
225  QList<farsa::ParameterSettableUIViewer> viewers;
226 
227  BaseExperimentGUI* gui = new BaseExperimentGUI(this, parent, flags);
228  viewers.append(farsa::ParameterSettableUIViewer(gui, "Experiment Control"));
229 
230  return viewers;
231 }
232 
234 {
235  // The default implementation does nothing
236 }
237 
239 {
240  // Executing the next action. The action ID should be a positive number and be in the vector. Moreover
241  // the action should use a separate thread
242  Q_ASSERT(m_runningOperationID >= 0);
243  Q_ASSERT(m_runningOperationID < m_operationsVector.size());
244  Q_ASSERT(m_operationsVector[m_runningOperationID]->useSeparateThread);
245 
246  // Start operation. Here we also signal that the operation has started/ended. Notice that we are calling a non thread-safe function
247  // from a thread different from the one in which this object lives, but it is OK since when we are here we are not calling this
248  // function from other places
249  uploadNewOperationStatus(__BaseExperiment_internal::OperationStatus::OperationStarted, m_runningOperationID);
250  m_operationsVector[m_runningOperationID]->executeOperation();
251  uploadNewOperationStatus(__BaseExperiment_internal::OperationStatus::OperationEnded, m_runningOperationID);
252 }
253 
255 {
256  QMutexLocker locker(&m_mutex);
257 
258  m_stop = true;
259 
260  // Also resuming the operation if it was sleeping
261  m_pause = false;
262  m_waitCondition.wakeAll();
263 }
264 
266 {
267  QMutexLocker locker(&m_mutex);
268 
269  // This is made this way so that m_pause is modified even if nothing is running (to be able to decide how the simulation starts)
270  m_pause = true;
271 }
272 
274 {
275  QMutexLocker locker(&m_mutex);
276 
277  if ((m_runningOperationID != -1) && m_operationsVector[m_runningOperationID]->steppable && m_pause) {
278  m_waitCondition.wakeAll();
279  }
280 }
281 
283 {
284  QMutexLocker locker(&m_mutex);
285 
286  // This is made this way so that m_pause is modified even if nothing is running (to be able to decide how the simulation starts)
287  const bool nowPaused = m_pause;
288  m_pause = false;
289 
290  if ((m_runningOperationID != -1) && m_operationsVector[m_runningOperationID]->steppable && nowPaused) {
291  m_waitCondition.wakeAll();
292  }
293 }
294 
295 void BaseExperiment::changeInterval(unsigned long interval)
296 {
297  changeInterval(interval, true);
298 }
299 
300 unsigned long BaseExperiment::currentInterval() const
301 {
302  return m_delay;
303 }
304 
305 const QVector<BaseExperiment::AbstractOperationWrapper*>& BaseExperiment::getOperations() const
306 {
307  return m_operationsVector;
308 }
309 
311 {
312  return &m_dataExchange;
313 }
314 
316 {
317  // Stopping the current operation, if running
318  m_workerThread->stopCurrentOperation(wait);
319 }
320 
322 {
323  stopCurrentOperation(false);
324 }
325 
326 QList<QAction*> BaseExperiment::getActionsForOperations(QObject* actionsParent) const
327 {
328  QList<QAction*> actions;
329 
330  for (int i = 0; i < m_operationsVector.size(); i++) {
331  // Creating the new action
332  QAction* act = new QAction(m_operationsVector[i]->name, actionsParent);
333 
334  // Connecting the signal of the new QAction to the m_actionSignalsMapper slot and setting
335  // the mapping
336  connect(act, SIGNAL(triggered()), m_actionSignalsMapper.get(), SLOT(map()));
337  m_actionSignalsMapper->setMapping(act, i);
338 
339  // Adding to the list of actions
340  actions.push_back(act);
341  }
342 
343  return actions;
344 }
345 
347 {
348  return m_batchRunning;
349 }
350 
352 {
353  QMutexLocker locker(&m_mutex);
354 
355  return m_stop;
356 }
357 
359 {
360  QMutexLocker locker(&m_mutex);
361 
362  if ((m_runningOperationID == -1) || (!m_operationsVector[m_runningOperationID]->steppable)) {
363  return;
364  }
365 
366  if (m_pause) {
367  if (m_previousPauseStatus == false) {
368  uploadNewOperationStatus(__BaseExperiment_internal::OperationStatus::OperationPaused, m_runningOperationID);
369  }
370  m_waitCondition.wait(&m_mutex);
371  if (m_pause == false) {
372  uploadNewOperationStatus(__BaseExperiment_internal::OperationStatus::OperationResumed, m_runningOperationID);
373  }
374  } else {
375  if (m_previousPauseStatus == true) {
376  uploadNewOperationStatus(__BaseExperiment_internal::OperationStatus::OperationResumed, m_runningOperationID);
377  }
378  if (m_delay != 0) {
379  m_waitCondition.wait(&m_mutex, m_delay);
380  }
381  }
382  m_previousPauseStatus = m_pause;
383 }
384 
385 void BaseExperiment::exceptionDuringOperation(BaseException *e)
386 {
387  Logger::error(QString("Error while executing the current operation, an exception was thrown. Reason: ") + e->what());
388 
389  // Here we laso have to signal the GUI that the operation has stopped
390  uploadNewOperationStatus(__BaseExperiment_internal::OperationStatus::OperationEnded, m_runningOperationID);
391 }
392 
393 void BaseExperiment::runOperation(int operationID)
394 {
395  // Executing the next action. The action ID should be a positive number and be in the vector
396  Q_ASSERT(operationID >= 0);
397  Q_ASSERT(operationID < m_operationsVector.size());
398 
399  m_runningOperationID = operationID;
400  if ((m_batchRunning) || (!m_operationsVector[m_runningOperationID]->useSeparateThread)) {
401  // Resetting stop
402  resetStop();
403 
404  // Starting operation directly. Here we also signal when the operation starts and ends
405  uploadNewOperationStatus(__BaseExperiment_internal::OperationStatus::OperationStarted, m_runningOperationID);
406  m_operationsVector[m_runningOperationID]->executeOperation();
407  uploadNewOperationStatus(__BaseExperiment_internal::OperationStatus::OperationEnded, m_runningOperationID);
408  } else {
409  // Checking if another operation is running. If it is, prints a warning and doesn't do anything. Here there is
410  // a possible race condition: if an operation is scheduled after the condition in the if is cheked but before
411  // addOperation is executed we could end up with two operations in the queue. However this is the only function
412  // that can schedule operations and this is never run concurrently. Moreover having two operations in the
413  // queue of the worker thread is not a big problem.
414  if (m_workerThread->operationRunning()) {
415  Logger::error("Cannot run the requested operation because another action is currently running; please wait until it finish, or stop it before");
416  return;
417  }
418 
419  // Resetting stop
420  resetStop();
421 
422  // Starting operation
423  m_workerThread->addOperation(this, false);
424  }
425 }
426 
427 void BaseExperiment::resetStop()
428 {
429  QMutexLocker locker(&m_mutex);
430 
431  m_stop = false;
432 }
433 
434 void BaseExperiment::uploadNewOperationStatus(__BaseExperiment_internal::OperationStatus::Status status, unsigned int operationID, unsigned long newDelay)
435 {
436  DatumToUpload<__BaseExperiment_internal::OperationStatus> d(m_dataExchange);
437  d->status = status;
438  d->operationID = operationID;
439  d->delay = newDelay;
440 }
441 
442 void BaseExperiment::changeInterval(unsigned long interval, bool sendNotificationToGUI)
443 {
444  QMutexLocker locker(&m_mutex);
445 
446  m_delay = interval;
447 
448  if (sendNotificationToGUI) {
449  uploadNewOperationStatus(__BaseExperiment_internal::OperationStatus::OperationStepDelayChanged, m_runningOperationID, m_delay);
450  }
451 }
452 
453 } // End namespace farsa
454