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 
141 BaseExperiment::BaseExperimentFlowController::BaseExperimentFlowController(BaseExperiment* baseExperiment)
142  : FlowController()
143  , m_baseExperiment(baseExperiment)
144 {
145 }
146 
147 bool BaseExperiment::BaseExperimentFlowController::stop()
148 {
149  return m_baseExperiment->stopSimulation();
150 }
151 
152 void BaseExperiment::BaseExperimentFlowController::pause()
153 {
154  m_baseExperiment->checkPause();
155 }
156 
158  : Component()
160  , ThreadOperation()
161  , FlowControlled()
162  , m_operationsVector()
163  , m_actionSignalsMapper(new QSignalMapper(NULL)) // parent is NULL because we take care of deleting this object by ourself
164  , m_workerThread(new WorkerThread(NULL)) // parent is NULL because we take care of deleting this object by ourself
165  , m_runningOperationID(-1)
166  , m_batchRunning(false)
167  , m_stop(false)
168  , m_mutex()
169  , m_waitCondition()
170  , m_pause(false)
171  , m_previousPauseStatus(false)
172  , m_delay(0)
173  , m_notifee(*this)
174  , m_dataExchange(2, DataUploaderDownloader<__BaseExperiment_internal::OperationStatus, __BaseExperiment_internal::OperationControl>::OverrideOlder, &m_notifee)
175  , m_flowController(this)
176 {
177  // Setting our flow controller
178  setFlowController(&m_flowController);
179 
180  // Disabling the check of association before upload because we could never get associated (e.g. when running in batch)
181  m_dataExchange.checkAssociationBeforeUpload(false);
182 
183  // We have to add the stop() operation
184  addOperation("stopCurrentOperation", &BaseExperiment::stopCurrentOperation, false, false);
185 
186  // Connecting the mapped signal of m_actionSignalsMapper to our slot
187  connect(m_actionSignalsMapper.get(), SIGNAL(mapped(int)), this, SLOT(runOperation(int)));
188  connect(m_workerThread.get(), SIGNAL(exceptionDuringOperation(farsa::BaseException*)), this, SLOT(exceptionDuringOperation(farsa::BaseException*)), Qt::BlockingQueuedConnection);
189 }
190 
192 {
193  // Stopping the thread
194  m_workerThread->quit();
195 
196  // Removing all operation wrappers
197  foreach (AbstractOperationWrapper* op, m_operationsVector) {
198  delete op;
199  }
200 }
201 
203 {
204  // Reading whether we are running in batch mode or not. The parameter is only present if we are running in batch,
205  // so we must use false as default value for m_batchRunning
206  m_batchRunning = ConfigurationHelper::getBool(params, "__INTERNAL__/BatchRunning", false);
207 }
208 
209 void BaseExperiment::save(ConfigurationParameters& params, QString prefix)
210 {
211  // This should always be called
212  params.startObjectParameters(prefix, "BaseExperiment", this);
213 }
214 
215 void BaseExperiment::describe(QString type)
216 {
217  Descriptor d = addTypeDescription(type, "The base class of experiments");
218 }
219 
221 {
222  // Starting the inner thread
223  m_workerThread->start();
224 
225  setStatus("Configured");
226 }
227 
229 {
231 }
232 
233 void BaseExperiment::fillActionsMenu(QMenu* actionsMenu)
234 {
235  // This simply gets the list of actions from getActionsForOperations() and then adds them to the menu
236  QList<QAction*> actions = getActionsForOperations(actionsMenu);
237 
238  foreach (QAction* a, actions) {
239  actionsMenu->addAction(a);
240  }
241 }
242 
243 QList<ParameterSettableUIViewer> BaseExperiment::getViewers(QWidget* parent, Qt::WindowFlags flags)
244 {
245  // The default implementation only adds the BaseExperimentGUI widget
246  QList<farsa::ParameterSettableUIViewer> viewers;
247 
248  BaseExperimentGUI* gui = new BaseExperimentGUI(this, parent, flags);
249  viewers.append(farsa::ParameterSettableUIViewer(gui, "Experiment Control"));
250 
251  return viewers;
252 }
253 
255 {
256  // The default implementation does nothing
257 }
258 
260 {
261  // Executing the next action. The action ID should be a positive number and be in the vector. Moreover
262  // the action should use a separate thread
263  Q_ASSERT(m_runningOperationID >= 0);
264  Q_ASSERT(m_runningOperationID < m_operationsVector.size());
265  Q_ASSERT(m_operationsVector[m_runningOperationID]->useSeparateThread);
266 
267  // Start operation. Here we also signal that the operation has started/ended. Notice that we are calling a non thread-safe function
268  // 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
269  // function from other places
270  uploadNewOperationStatus(__BaseExperiment_internal::OperationStatus::OperationStarted, m_runningOperationID);
271  m_operationsVector[m_runningOperationID]->executeOperation();
272  uploadNewOperationStatus(__BaseExperiment_internal::OperationStatus::OperationEnded, m_runningOperationID);
273 }
274 
276 {
277  QMutexLocker locker(&m_mutex);
278 
279  m_stop = true;
280 
281  // Also resuming the operation if it was sleeping
282  m_pause = false;
283  m_waitCondition.wakeAll();
284 }
285 
287 {
288  QMutexLocker locker(&m_mutex);
289 
290  // 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)
291  m_pause = true;
292 }
293 
295 {
296  QMutexLocker locker(&m_mutex);
297 
298  if ((m_runningOperationID != -1) && m_operationsVector[m_runningOperationID]->steppable && m_pause) {
299  m_waitCondition.wakeAll();
300  }
301 }
302 
304 {
305  QMutexLocker locker(&m_mutex);
306 
307  // 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)
308  const bool nowPaused = m_pause;
309  m_pause = false;
310 
311  if ((m_runningOperationID != -1) && m_operationsVector[m_runningOperationID]->steppable && nowPaused) {
312  m_waitCondition.wakeAll();
313  }
314 }
315 
316 void BaseExperiment::changeInterval(unsigned long interval)
317 {
318  changeInterval(interval, true);
319 }
320 
321 unsigned long BaseExperiment::currentInterval() const
322 {
323  return m_delay;
324 }
325 
326 const QVector<BaseExperiment::AbstractOperationWrapper*>& BaseExperiment::getOperations() const
327 {
328  return m_operationsVector;
329 }
330 
332 {
333  return &m_dataExchange;
334 }
335 
337 {
338  // Stopping the current operation, if running
339  m_workerThread->stopCurrentOperation(wait);
340 }
341 
343 {
344  stopCurrentOperation(false);
345 }
346 
347 QList<QAction*> BaseExperiment::getActionsForOperations(QObject* actionsParent) const
348 {
349  QList<QAction*> actions;
350 
351  for (int i = 0; i < m_operationsVector.size(); i++) {
352  // Creating the new action
353  QAction* act = new QAction(m_operationsVector[i]->name, actionsParent);
354 
355  // Connecting the signal of the new QAction to the m_actionSignalsMapper slot and setting
356  // the mapping
357  connect(act, SIGNAL(triggered()), m_actionSignalsMapper.get(), SLOT(map()));
358  m_actionSignalsMapper->setMapping(act, i);
359 
360  // Adding to the list of actions
361  actions.push_back(act);
362  }
363 
364  return actions;
365 }
366 
368 {
369  return m_batchRunning;
370 }
371 
372 bool BaseExperiment::stopSimulation()
373 {
374  QMutexLocker locker(&m_mutex);
375 
376  return m_stop;
377 }
378 
379 void BaseExperiment::checkPause()
380 {
381  QMutexLocker locker(&m_mutex);
382 
383  if ((m_runningOperationID == -1) || (!m_operationsVector[m_runningOperationID]->steppable)) {
384  return;
385  }
386 
387  if (m_pause) {
388  if (m_previousPauseStatus == false) {
389  uploadNewOperationStatus(__BaseExperiment_internal::OperationStatus::OperationPaused, m_runningOperationID);
390  }
391  m_waitCondition.wait(&m_mutex);
392  if (m_pause == false) {
393  uploadNewOperationStatus(__BaseExperiment_internal::OperationStatus::OperationResumed, m_runningOperationID);
394  }
395  } else {
396  if (m_previousPauseStatus == true) {
397  uploadNewOperationStatus(__BaseExperiment_internal::OperationStatus::OperationResumed, m_runningOperationID);
398  }
399  if (m_delay != 0) {
400  m_waitCondition.wait(&m_mutex, m_delay);
401  }
402  }
403  m_previousPauseStatus = m_pause;
404 }
405 
406 void BaseExperiment::exceptionDuringOperation(BaseException *e)
407 {
408  Logger::error(QString("Error while executing the current operation, an exception was thrown. Reason: ") + e->what());
409 
410  // Here we laso have to signal the GUI that the operation has stopped
411  uploadNewOperationStatus(__BaseExperiment_internal::OperationStatus::OperationEnded, m_runningOperationID);
412 }
413 
414 void BaseExperiment::runOperation(int operationID)
415 {
416  // Executing the next action. The action ID should be a positive number and be in the vector
417  Q_ASSERT(operationID >= 0);
418  Q_ASSERT(operationID < m_operationsVector.size());
419 
420  m_runningOperationID = operationID;
421  if ((m_batchRunning) || (!m_operationsVector[m_runningOperationID]->useSeparateThread)) {
422  // Resetting stop
423  resetStop();
424 
425  // Starting operation directly. Here we also signal when the operation starts and ends
426  uploadNewOperationStatus(__BaseExperiment_internal::OperationStatus::OperationStarted, m_runningOperationID);
427  m_operationsVector[m_runningOperationID]->executeOperation();
428  uploadNewOperationStatus(__BaseExperiment_internal::OperationStatus::OperationEnded, m_runningOperationID);
429  } else {
430  // Checking if another operation is running. If it is, prints a warning and doesn't do anything. Here there is
431  // a possible race condition: if an operation is scheduled after the condition in the if is cheked but before
432  // addOperation is executed we could end up with two operations in the queue. However this is the only function
433  // that can schedule operations and this is never run concurrently. Moreover having two operations in the
434  // queue of the worker thread is not a big problem.
435  if (m_workerThread->operationRunning()) {
436  Logger::error("Cannot run the requested operation because another action is currently running; please wait until it finish, or stop it before");
437  return;
438  }
439 
440  // Resetting stop
441  resetStop();
442 
443  // Starting operation
444  m_workerThread->addOperation(this, false);
445  }
446 }
447 
448 void BaseExperiment::resetStop()
449 {
450  QMutexLocker locker(&m_mutex);
451 
452  m_stop = false;
453 }
454 
455 void BaseExperiment::uploadNewOperationStatus(__BaseExperiment_internal::OperationStatus::Status status, unsigned int operationID, unsigned long newDelay)
456 {
457  DatumToUpload<__BaseExperiment_internal::OperationStatus> d(m_dataExchange);
458  d->status = status;
459  d->operationID = operationID;
460  d->delay = newDelay;
461 }
462 
463 void BaseExperiment::changeInterval(unsigned long interval, bool sendNotificationToGUI)
464 {
465  QMutexLocker locker(&m_mutex);
466 
467  m_delay = interval;
468 
469  if (sendNotificationToGUI) {
470  uploadNewOperationStatus(__BaseExperiment_internal::OperationStatus::OperationStepDelayChanged, m_runningOperationID, m_delay);
471  }
472 }
473 
474 } // End namespace farsa
475 
void changeInterval(unsigned long interval)
Changes the delay between subsequent steps of a steppable operation.
virtual ParameterSettableUI * getUIManager()
Returns an instance of the class handling our viewers.
virtual void run()
Runs the next operation.
void resume()
Resumes the current operation if paused.
virtual QList< ParameterSettableUIViewer > getViewers(QWidget *parent, Qt::WindowFlags flags)
Returns the list of viewers.
The base for classes that have a controllable flow of execution.
Definition: flowcontrol.h:170
virtual void addAdditionalMenus(QMenuBar *menuBar)
Adds additional menus to the menu bar of Total99.
QString status()
return a text description of the current status of the component
Definition: component.h:53
virtual void save(ConfigurationParameters &params, QString prefix)
Saves the actual status of parameters into the ConfigurationParameters object passed.
virtual void addAdditionalMenus(QMenuBar *menuBar)
Adds additional menus to the menu bar of Total99.
void addOperation(QString name, void(T::*func)(), bool useSeparateThread, bool steppable)
The function that adds the given operation to the list of all operations declared by the experiment...
The GUI to control a BaseExperiment subclass.
unsigned long currentInterval() const
Returns the current delay for steppable operations.
The base abstract class for operation wrappers.
void checkAssociationBeforeUpload(bool v)
static void describe(QString type)
Add to Factory::typeDescriptions() the descriptions of all parameters and subgroups.
virtual void fillActionsMenu(QMenu *actionsMenu)
Fills the menu "Actions" of Total99.
static bool getBool(ConfigurationParameters &params, QString paramPath, bool def=false)
static void error(QString msg)
const QVector< AbstractOperationWrapper * > & getOperations() const
Returns the list of operations.
DataUploaderDownloader< __BaseExperiment_internal::OperationStatus, __BaseExperiment_internal::OperationControl > * getUploaderDownloader()
Returns the object to exchange data.
bool startObjectParameters(QString groupPath, QString typeName, ParameterSettable *object)
virtual void stopCurrentOperation()
Forces the end of the current operation, if threaded.
void setStatus(QString newStatus)
used by subclasses to change the status of the experiment
Definition: component.h:61
static Descriptor addTypeDescription(QString type, QString shortHelp, QString longHelp=QString(""))
void setFlowController(FlowController *flowController)
Sets the flow controller object to use.
Definition: flowcontrol.h:206
virtual void postConfigureInitialization()
This function is called after all linked objects have been configured.
The Component is the base (abstract) class for any specific project implementation.
Definition: component.h:45
void pause()
Puts the current operation in pause.
virtual void fillActionsMenu(QMenu *actionsMenu)
Fills the menu "Actions" of Total99.
virtual void configure(ConfigurationParameters &params, QString prefix)
Configures the object using a ConfigurationParameters object.
bool batchRunning() const
Returns true if we are running in batch.
BaseExperimentUIManager(BaseExperiment *experiment)
Constructor.
virtual void stop()
Forces the end of the experiment.
BaseExperiment()
Constructor.
The base class for experiments.
virtual QList< ParameterSettableUIViewer > getViewers(QWidget *parent, Qt::WindowFlags flags)
Returns the list of viewers.
void step()
Performs a single step for the current steppable operation.
QList< QAction * > getActionsForOperations(QObject *actionsParent) const
Returns a list of actions, one for each operation declared by the experiment.
virtual ~BaseExperiment()
Destructor.