source: src/Actions/ActionQueue.cpp@ 0ec9f5

Candidate_v1.7.0 stable
Last change on this file since 0ec9f5 was 0ec9f5, checked in by Frederik Heber <frederik.heber@…>, 5 years ago

Added UndoMarkAction.

NOTE: This action is necessary as not all actions are actually recorded
in the history. For example, the UndoAction is an action that is not
pushed into the history deque and also must not as further undos would
then become impossible. There are other actions that just do output
or similar things that do not change the state.
This makes it impossible to undo back to a certain state by blindly
counting actions as one cannot know from the outside whether an action
is stateless or not.

undoing till the set mark.

  • TESTS: added regression test case on undo-mark.
  • Property mode set to 100644
File size: 12.1 KB
Line 
1/*
2 * Project: MoleCuilder
3 * Description: creates and alters molecular systems
4 * Copyright (C) 2013 Frederik Heber. All rights reserved.
5 *
6 *
7 * This file is part of MoleCuilder.
8 *
9 * MoleCuilder is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * MoleCuilder is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with MoleCuilder. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23/*
24 * ActionQueue.cpp
25 *
26 * Created on: Aug 16, 2013
27 * Author: heber
28 */
29
30// include config.h
31#ifdef HAVE_CONFIG_H
32#include <config.h>
33#endif
34
35//#include "CodePatterns/MemDebug.hpp"
36
37#include "Actions/ActionQueue.hpp"
38
39#include "CodePatterns/Assert.hpp"
40#include "CodePatterns/IteratorAdaptors.hpp"
41#include "CodePatterns/Log.hpp"
42#include "CodePatterns/Singleton_impl.hpp"
43
44#include <boost/date_time/posix_time/posix_time.hpp>
45#include <boost/version.hpp>
46#include <iterator>
47#include <string>
48#include <sstream>
49#include <vector>
50
51#include "Actions/ActionExceptions.hpp"
52#include "Actions/ActionHistory.hpp"
53#include "Actions/ActionRegistry.hpp"
54#include "Actions/MakroAction.hpp"
55#include "Actions/Process.hpp"
56#include "World.hpp"
57
58using namespace MoleCuilder;
59
60const Action* ActionQueue::_lastchangedaction = NULL;
61
62ActionQueue::ActionQueue() :
63 Observable("ActionQueue"),
64 AR(new ActionRegistry()),
65 history(new ActionHistory),
66 lastActionOk(true),
67 CurrentAction(0),
68#ifdef HAVE_ACTION_THREAD
69 run_thread(boost::bind(&ActionQueue::run, this)),
70 run_thread_isIdle(true),
71#endif
72 dryrun_flag(false)
73{
74 // channels of observable
75 Channels *OurChannel = new Channels;
76 Observable::insertNotificationChannel( std::make_pair(static_cast<Observable *>(this), OurChannel) );
77 // add instance for each notification type
78 for (size_t type = 0; type < NotificationType_MAX; ++type)
79 OurChannel->addChannel(type);
80}
81
82ActionQueue::~ActionQueue()
83{
84#ifdef HAVE_ACTION_THREAD
85 stop();
86
87 clearTempQueue();
88#endif
89
90 clearQueue();
91
92 delete history;
93 delete AR;
94}
95
96void ActionQueue::queueAction(const std::string &name, enum Action::QueryOptions state)
97{
98 const Action & registryaction = AR->getActionByName(name);
99 queueAction(&registryaction, state);
100}
101
102void ActionQueue::queueAction(const Action * const _action, enum Action::QueryOptions state)
103{
104 Action *newaction = _action->clone(state);
105 newaction->prepare(state);
106#ifdef HAVE_ACTION_THREAD
107 mtx_queue.lock();
108#endif
109 actionqueue.push_back( newaction );
110#ifndef HAVE_ACTION_THREAD
111 try {
112 if (!isDryRun(newaction)) {
113 CurrentAction = actionqueue.size()-1;
114 newaction->call();
115 CurrentAction = actionqueue.size();
116 }
117 lastActionOk = true;
118 } catch(ActionFailureException &e) {
119 std::cerr << "Action " << *boost::get_error_info<ActionNameString>(e) << " has failed." << std::endl;
120 World::getInstance().setExitFlag(5);
121 clearQueue(actionqueue.size()-1);
122 lastActionOk = false;
123 std::cerr << "Remaining Actions cleared from queue." << std::endl;
124 } catch (std::exception &e) {
125 pushStatus("FAIL: General exception caught, aborting.");
126 World::getInstance().setExitFlag(134);
127 clearQueue(actionqueue.size()-1);
128 lastActionOk = false;
129 std::cerr << "Remaining Actions cleared from queue." << std::endl;
130 }
131 if (lastActionOk) {
132 OBSERVE;
133 NOTIFY(ActionQueued);
134 _lastchangedaction = newaction;
135 }
136#else
137 mtx_queue.unlock();
138 setRunThreadIdle(isIdle());
139#endif
140}
141
142void ActionQueue::insertAction(Action *_action, enum Action::QueryOptions state)
143{
144#ifndef HAVE_ACTION_THREAD
145 queueAction(_action, state);
146#else
147 Action *newaction = _action->clone(state);
148 newaction->prepare(state);
149 mtx_queue.lock();
150 tempqueue.push_back( newaction );
151 const bool tempqueue_notempty = !tempqueue.empty();
152 mtx_queue.unlock();
153 setRunThreadIdle( !((!isIdle()) || tempqueue_notempty) );
154#endif
155}
156
157bool ActionQueue::isIdle() const
158{
159#ifdef HAVE_ACTION_THREAD
160 boost::unique_lock<boost::mutex> lock(mtx_queue);
161#endif
162 bool status = (CurrentAction == actionqueue.size());
163 return status;
164}
165
166bool ActionQueue::isProcess() const
167{
168 if (isIdle())
169 return false;
170 const Process *possibleprocess = dynamic_cast<const Process *>(&getCurrentAction());
171 if (possibleprocess == NULL)
172 return false;
173 else
174 return true;
175}
176
177bool ActionQueue::isMakroAction() const
178{
179 if (isIdle())
180 return false;
181 const MakroAction *possiblemakroaction = dynamic_cast<const MakroAction *>(&getCurrentAction());
182 if (possiblemakroaction == NULL)
183 return false;
184 else
185 return true;
186}
187
188const Action& ActionQueue::getCurrentAction() const
189{
190 return *const_cast<const Action *>(actionqueue[CurrentAction]);
191}
192
193#ifdef HAVE_ACTION_THREAD
194void ActionQueue::run()
195{
196 bool Interrupted = false;
197 do {
198 // sleep for some time and wait for queue to fill up again
199 try {
200#if BOOST_VERSION < 105000
201 run_thread.sleep(boost::get_system_time() + boost::posix_time::milliseconds(100));
202#else
203 boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
204#endif
205 } catch(boost::thread_interrupted &e) {
206 LOG(2, "INFO: ActionQueue has received stop signal.");
207 Interrupted = true;
208 }
209// LOG(1, "DEBUG: Start of ActionQueue's run() loop.");
210 // call all currently present Actions
211 mtx_queue.lock();
212 insertTempQueue();
213 mtx_queue.unlock();
214 bool status = !isIdle();
215 while (status) {
216 // boost::this_thread::disable_interruption di;
217 LOG(0, "Calling Action " << actionqueue[CurrentAction]->getName() << " ... ");
218 try {
219 if (!isDryRun(actionqueue[CurrentAction]))
220 actionqueue[CurrentAction]->call();
221 pushStatus("SUCCESS: Action "+actionqueue[CurrentAction]->getName()+" successful.");
222 lastActionOk = true;
223 } catch(ActionFailureException &e) {
224 pushStatus("FAIL: Action "+*boost::get_error_info<ActionNameString>(e)+" has failed.");
225 World::getInstance().setExitFlag(5);
226 clearQueue(CurrentAction);
227 clearTempQueue();
228 lastActionOk = false;
229 std::cerr << "Remaining Actions cleared from queue." << std::endl;
230 } catch (std::exception &e) {
231 pushStatus("FAIL: General exception caught, aborting.");
232 World::getInstance().setExitFlag(134);
233 clearQueue(CurrentAction);
234 clearTempQueue();
235 lastActionOk = false;
236 std::cerr << "Remaining Actions cleared from queue." << std::endl;
237 }
238 if (lastActionOk) {
239 OBSERVE;
240 NOTIFY(ActionQueued);
241 _lastchangedaction = actionqueue[CurrentAction];
242 mtx_queue.lock();
243 CurrentAction++;
244 mtx_queue.unlock();
245 }
246 // access actionqueue, hence using mutex
247 mtx_queue.lock();
248 // insert new actions (before [CurrentAction]) if they have been spawned
249 // we must have an extra vector for this, as we cannot change actionqueue
250 // while an action instance is "in-use"
251 insertTempQueue();
252 mtx_queue.unlock();
253 status = !isIdle();
254 }
255 mtx_queue.lock();
256 const bool tempqueue_notempty = !tempqueue.empty();
257 mtx_queue.unlock();
258 setRunThreadIdle( !((!isIdle()) || tempqueue_notempty) );
259 cond_idle.notify_one();
260// LOG(1, "DEBUG: End of ActionQueue's run() loop.");
261 } while (!Interrupted);
262}
263
264void ActionQueue::insertTempQueue()
265{
266 if (!tempqueue.empty()) {
267 ActionQueue_t::iterator InsertionIter = actionqueue.begin();
268 std::advance(InsertionIter, CurrentAction);
269 actionqueue.insert( InsertionIter, tempqueue.begin(), tempqueue.end() );
270 tempqueue.clear();
271 }
272}
273
274void ActionQueue::wait()
275{
276 boost::unique_lock<boost::mutex> lock(mtx_idle);
277 while(!run_thread_isIdle)
278 {
279 cond_idle.wait(lock);
280 }
281}
282#endif
283
284#ifdef HAVE_ACTION_THREAD
285void ActionQueue::stop()
286{
287 // notify actionqueue thread that we wish to terminate
288 run_thread.interrupt();
289 // wait till it ends
290 run_thread.join();
291}
292#endif
293
294const Action& ActionQueue::getActionByName(const std::string &name)
295{
296 return AR->getActionByName(name);
297}
298
299bool ActionQueue::isActionKnownByName(const std::string &name) const
300{
301 return AR->isActionPresentByName(name);
302}
303
304void ActionQueue::registerAction(Action *_action)
305{
306 AR->registerInstance(_action);
307}
308
309void ActionQueue::outputAsCLI(std::ostream &output) const
310{
311 for (ActionQueue_t::const_iterator iter = actionqueue.begin();
312 iter != actionqueue.end();
313 ++iter) {
314 // skip store-session in printed list
315 if ( ((*iter)->getName() != std::string("store-session"))
316 && ((*iter)->getName() != std::string("load-session"))) {
317 if (iter != actionqueue.begin())
318 output << " ";
319 (*iter)->outputAsCLI(output);
320 }
321 }
322 output << std::endl;
323}
324
325void ActionQueue::outputAsPython(std::ostream &output) const
326{
327 const std::string prefix("pyMoleCuilder");
328 output << "import " << prefix << std::endl;
329 output << "# ========================== Stored Session BEGIN ==========================" << std::endl;
330 for (ActionQueue_t::const_iterator iter = actionqueue.begin();
331 iter != actionqueue.end();
332 ++iter) {
333 // skip store-session in printed list
334 if ( ((*iter)->getName() != std::string("store-session"))
335 && ((*iter)->getName() != std::string("load-session")))
336 (*iter)->outputAsPython(output, prefix);
337 }
338 output << "# =========================== Stored Session END ===========================" << std::endl;
339}
340
341const ActionTrait& ActionQueue::getActionsTrait(const std::string &name) const
342{
343 // this const_cast is just required as long as we have a non-const getActionByName
344 const Action & action = const_cast<ActionQueue *>(this)->getActionByName(name);
345 return action.Traits;
346}
347
348void ActionQueue::addElement(Action* _Action,ActionState::ptr _state)
349{
350 history->addElement(_Action, _state);
351}
352
353void ActionQueue::clear()
354{
355 history->clear();
356}
357
358void ActionQueue::clearQueue(const size_t _fromAction)
359{
360#ifdef HAVE_ACTION_THREAD
361 mtx_queue.lock();
362#endif
363 LOG(1, "Removing all Actions from position " << _fromAction << " onward.");
364 // free all actions still to be called contained in actionqueue
365 ActionQueue_t::iterator inititer = actionqueue.begin();
366 std::advance(inititer, _fromAction);
367 for (ActionQueue_t::iterator iter = inititer; iter != actionqueue.end(); ++iter)
368 delete *iter;
369 actionqueue.erase(inititer, actionqueue.end());
370 LOG(1, "There are " << actionqueue.size() << " remaining Actions.");
371#ifdef HAVE_ACTION_THREAD
372 CurrentAction = actionqueue.size();
373 mtx_queue.unlock();
374#endif
375}
376
377#ifdef HAVE_ACTION_THREAD
378void ActionQueue::clearTempQueue()
379{
380 // free all actions contained in tempqueue
381 for (ActionQueue_t::iterator iter = tempqueue.begin();
382 !tempqueue.empty(); iter = tempqueue.begin()) {
383 delete *iter;
384 tempqueue.erase(iter);
385 }
386}
387
388void ActionQueue::setRunThreadIdle(const bool _flag)
389{
390 {
391 boost::unique_lock<boost::mutex> lock(mtx_idle);
392 run_thread_isIdle = _flag;
393 }
394}
395#endif
396
397const ActionQueue::ActionTokens_t ActionQueue::getListOfActions() const
398{
399 ActionTokens_t returnlist;
400
401 returnlist.insert(
402 returnlist.end(),
403 MapKeyConstIterator<ActionRegistry::const_iterator>(AR->getBeginIter()),
404 MapKeyConstIterator<ActionRegistry::const_iterator>(AR->getEndIter()));
405
406 return returnlist;
407}
408
409void ActionQueue::undoLast()
410{
411 history->undoLast();
412}
413
414void ActionQueue::setMark() {
415 history->setMark();
416}
417
418void ActionQueue::unsetMark() {
419 history->unsetMark();
420}
421
422void ActionQueue::undoTillMark()
423{
424 history->undoTillMark();
425}
426
427bool ActionQueue::canUndo() const
428{
429 return history->hasUndo();
430}
431
432void ActionQueue::redoLast()
433{
434 history->redoLast();
435}
436
437bool ActionQueue::canRedo() const
438{
439 return history->hasRedo();
440}
441
442bool ActionQueue::isDryRun(const Action *_nextaction) const
443{
444 bool status = dryrun_flag;
445 status &= (_nextaction->getName() != "no-dry-run");
446 return status;
447}
448
449CONSTRUCT_SINGLETON(ActionQueue)
Note: See TracBrowser for help on using the repository browser.