openMSX
AfterCommand.cc
Go to the documentation of this file.
1 #include "AfterCommand.hh"
2 #include "CommandController.hh"
3 #include "CliComm.hh"
4 #include "Schedulable.hh"
5 #include "EventDistributor.hh"
6 #include "EventDistributor.hh"
7 #include "InputEventFactory.hh"
8 #include "Reactor.hh"
9 #include "MSXMotherBoard.hh"
10 #include "Alarm.hh"
11 #include "EmuTime.hh"
12 #include "CommandException.hh"
13 #include "Interpreter.hh"
14 #include "TclObject.hh"
15 #include "StringOp.hh"
16 #include "openmsx.hh"
17 #include "unreachable.hh"
18 #include "memory.hh"
19 #include <algorithm>
20 #include <iterator>
21 #include <cstdlib>
22 #include <sstream>
23 
24 using std::ostringstream;
25 using std::string;
26 using std::vector;
27 using std::unique_ptr;
28 using std::move;
29 
30 namespace openmsx {
31 
32 class AfterCmd
33 {
34 public:
35  virtual ~AfterCmd();
36  string_ref getCommand() const;
37  const string& getId() const;
38  virtual string getType() const = 0;
39  void execute();
40 protected:
42  const TclObject& command);
43  unique_ptr<AfterCmd> removeSelf();
44 
47  string id;
48  static unsigned lastAfterId;
49 };
50 
51 class AfterTimedCmd : public AfterCmd, private Schedulable
52 {
53 public:
54  double getTime() const;
55  void reschedule();
56 protected:
57  AfterTimedCmd(Scheduler& scheduler,
59  const TclObject& command, double time);
60 private:
61  virtual void executeUntil(EmuTime::param time, int userData);
62  virtual void schedulerDeleted();
63 
64  double time; // Zero when expired, otherwise the original duration (to
65  // be able to reschedule for 'after idle').
66 };
67 
69 {
70 public:
71  AfterTimeCmd(Scheduler& scheduler,
73  const TclObject& command, double time);
74  virtual string getType() const;
75 };
76 
78 {
79 public:
80  AfterIdleCmd(Scheduler& scheduler,
82  const TclObject& command, double time);
83  virtual string getType() const;
84 };
85 
86 template<EventType T>
87 class AfterEventCmd : public AfterCmd
88 {
89 public:
91  const TclObject& type,
92  const TclObject& command);
93  virtual string getType() const;
94 private:
95  const string type;
96 };
97 
99 {
100 public:
102  const AfterCommand::EventPtr& event,
103  const TclObject& command);
104  virtual string getType() const;
105  AfterCommand::EventPtr getEvent() const { return event; }
106 private:
108 };
109 
110 class AfterRealTimeCmd : public AfterCmd, private Alarm
111 {
112 public:
114  const TclObject& command, double time);
115  virtual ~AfterRealTimeCmd();
116  virtual string getType() const;
117  bool hasExpired() const;
118 
119 private:
120  virtual bool alarm();
121 
122  bool expired;
123 };
124 
125 
127  EventDistributor& eventDistributor_,
128  CommandController& commandController)
129  : Command(commandController, "after")
130  , reactor(reactor_)
131  , eventDistributor(eventDistributor_)
132 {
133  // TODO DETACHED <-> EMU types should be cleaned up
134  // (moved to event iso listener?)
135  eventDistributor.registerEventListener(
136  OPENMSX_KEY_UP_EVENT, *this);
137  eventDistributor.registerEventListener(
138  OPENMSX_KEY_DOWN_EVENT, *this);
139  eventDistributor.registerEventListener(
141  eventDistributor.registerEventListener(
143  eventDistributor.registerEventListener(
145  eventDistributor.registerEventListener(
147  eventDistributor.registerEventListener(
149  eventDistributor.registerEventListener(
151  eventDistributor.registerEventListener(
153  eventDistributor.registerEventListener(
154  OPENMSX_BREAK_EVENT, *this);
155  eventDistributor.registerEventListener(
156  OPENMSX_QUIT_EVENT, *this);
157  eventDistributor.registerEventListener(
158  OPENMSX_BOOT_EVENT, *this);
159  eventDistributor.registerEventListener(
161  eventDistributor.registerEventListener(
163  eventDistributor.registerEventListener(
165 }
166 
168 {
169  eventDistributor.unregisterEventListener(
171  eventDistributor.unregisterEventListener(
173  eventDistributor.unregisterEventListener(
175  eventDistributor.unregisterEventListener(
176  OPENMSX_BOOT_EVENT, *this);
177  eventDistributor.unregisterEventListener(
178  OPENMSX_QUIT_EVENT, *this);
179  eventDistributor.unregisterEventListener(
180  OPENMSX_BREAK_EVENT, *this);
181  eventDistributor.unregisterEventListener(
183  eventDistributor.unregisterEventListener(
185  eventDistributor.unregisterEventListener(
187  eventDistributor.unregisterEventListener(
189  eventDistributor.unregisterEventListener(
191  eventDistributor.unregisterEventListener(
193  eventDistributor.unregisterEventListener(
195  eventDistributor.unregisterEventListener(
196  OPENMSX_KEY_DOWN_EVENT, *this);
197  eventDistributor.unregisterEventListener(
198  OPENMSX_KEY_UP_EVENT, *this);
199 }
200 
201 void AfterCommand::execute(const vector<TclObject>& tokens, TclObject& result)
202 {
203  if (tokens.size() < 2) {
204  throw CommandException("Missing argument");
205  }
206  int time;
207  string_ref subCmd = tokens[1].getString();
208  if (subCmd == "time") {
209  afterTime(tokens, result);
210  } else if (subCmd == "realtime") {
211  afterRealTime(tokens, result);
212  } else if (subCmd == "idle") {
213  afterIdle(tokens, result);
214  } else if (subCmd == "frame") {
215  afterEvent<OPENMSX_FINISH_FRAME_EVENT>(tokens, result);
216  } else if (subCmd == "break") {
217  afterEvent<OPENMSX_BREAK_EVENT>(tokens, result);
218  } else if (subCmd == "quit") {
219  afterEvent<OPENMSX_QUIT_EVENT>(tokens, result);
220  } else if (subCmd == "boot") {
221  afterEvent<OPENMSX_BOOT_EVENT>(tokens, result);
222  } else if (subCmd == "machine_switch") {
223  afterEvent<OPENMSX_MACHINE_LOADED_EVENT>(tokens, result);
224  } else if (subCmd == "info") {
225  afterInfo(tokens, result);
226  } else if (subCmd == "cancel") {
227  afterCancel(tokens, result);
228  } else if (StringOp::stringToInt(subCmd.str(), time)) { // TODO
229  afterTclTime(time, tokens, result);
230  } else {
231  // try to interpret token as an event name
232  try {
233  afterInputEvent(
234  InputEventFactory::createInputEvent(subCmd.str()), // TODO
235  tokens, result);
236  } catch (MSXException&) {
237  throw SyntaxError();
238  }
239  }
240 }
241 
242 static double getTime(const TclObject& obj)
243 {
244  double time = obj.getDouble();
245  if (time < 0) {
246  throw CommandException("Not a valid time specification");
247  }
248  return time;
249 }
250 
251 void AfterCommand::afterTime(const vector<TclObject>& tokens, TclObject& result)
252 {
253  if (tokens.size() != 4) {
254  throw SyntaxError();
255  }
256  MSXMotherBoard* motherBoard = reactor.getMotherBoard();
257  if (!motherBoard) return;
258  double time = getTime(tokens[2]);
259  auto cmd = make_unique<AfterTimeCmd>(
260  motherBoard->getScheduler(), *this, tokens[3], time);
261  result.setString(cmd->getId());
262  afterCmds.push_back(move(cmd));
263 }
264 
265 void AfterCommand::afterRealTime(const vector<TclObject>& tokens, TclObject& result)
266 {
267  if (tokens.size() != 4) {
268  throw SyntaxError();
269  }
270  double time = getTime(tokens[2]);
271  auto cmd = make_unique<AfterRealTimeCmd>(
272  *this, tokens[3], time);
273  result.setString(cmd->getId());
274  afterCmds.push_back(move(cmd));
275 }
276 
277 void AfterCommand::afterTclTime(
278  int ms, const vector<TclObject>& tokens, TclObject& result)
279 {
280  TclObject command(tokens.front().getInterpreter());
281  command.addListElements(tokens.begin() + 2, tokens.end());
282  auto cmd = make_unique<AfterRealTimeCmd>(
283  *this, command, ms / 1000.0);
284  result.setString(cmd->getId());
285  afterCmds.push_back(move(cmd));
286 }
287 
288 template<EventType T>
289 void AfterCommand::afterEvent(const vector<TclObject>& tokens, TclObject& result)
290 {
291  if (tokens.size() != 3) {
292  throw SyntaxError();
293  }
294  auto cmd = make_unique<AfterEventCmd<T>>(
295  *this, tokens[1], tokens[2]);
296  result.setString(cmd->getId());
297  afterCmds.push_back(move(cmd));
298 }
299 
300 void AfterCommand::afterInputEvent(
301  const EventPtr& event, const vector<TclObject>& tokens, TclObject& result)
302 {
303  if (tokens.size() != 3) {
304  throw SyntaxError();
305  }
306  auto cmd = make_unique<AfterInputEventCmd>(
307  *this, event, tokens[2]);
308  result.setString(cmd->getId());
309  afterCmds.push_back(move(cmd));
310 }
311 
312 void AfterCommand::afterIdle(const vector<TclObject>& tokens, TclObject& result)
313 {
314  if (tokens.size() != 4) {
315  throw SyntaxError();
316  }
317  MSXMotherBoard* motherBoard = reactor.getMotherBoard();
318  if (!motherBoard) return;
319  double time = getTime(tokens[2]);
320  auto cmd = make_unique<AfterIdleCmd>(
321  motherBoard->getScheduler(), *this, tokens[3], time);
322  result.setString(cmd->getId());
323  afterCmds.push_back(move(cmd));
324 }
325 
326 void AfterCommand::afterInfo(const vector<TclObject>& /*tokens*/, TclObject& result)
327 {
328  ostringstream str;
329  for (auto& cmd : afterCmds) {
330  str << cmd->getId() << ": ";
331  str << cmd->getType() << ' ';
332  if (auto cmd2 = dynamic_cast<const AfterTimedCmd*>(cmd.get())) {
333  str.precision(3);
334  str << std::fixed << std::showpoint << cmd2->getTime() << ' ';
335  }
336  str << cmd->getCommand()
337  << '\n';
338  }
339  result.setString(str.str());
340 }
341 
342 void AfterCommand::afterCancel(const vector<TclObject>& tokens, TclObject& /*result*/)
343 {
344  if (tokens.size() != 3) {
345  throw SyntaxError();
346  }
347  if (tokens.size() == 3) {
348  for (auto it = afterCmds.begin(); it != afterCmds.end(); ++it) {
349  if ((*it)->getId() == tokens[2].getString()) {
350  afterCmds.erase(it);
351  return;
352  }
353  }
354  }
355  TclObject command;
356  command.addListElements(tokens.begin() + 2, tokens.end());
357  string_ref cmdStr = command.getString();
358  for (auto it = afterCmds.begin(); it != afterCmds.end(); ++it) {
359  if ((*it)->getCommand() == cmdStr) {
360  afterCmds.erase(it);
361  // Tcl manual is not clear about this, but it seems
362  // there's only occurence of this command canceled.
363  // It's also not clear which of the (possibly) several
364  // matches is canceled.
365  return;
366  }
367  }
368  // It's not an error if no match is found
369 }
370 
371 string AfterCommand::help(const vector<string>& /*tokens*/) const
372 {
373  return "after time <seconds> <command> execute a command after some time (MSX time)\n"
374  "after realtime <seconds> <command> execute a command after some time (realtime)\n"
375  "after idle <seconds> <command> execute a command after some time being idle\n"
376  "after frame <command> execute a command after a new frame is drawn\n"
377  "after break <command> execute a command after a breakpoint is reached\n"
378  "after boot <command> execute a command after a (re)boot\n"
379  "after machine_switch <command> execute a command after a switch to a new machine\n"
380  "after info list all postponed commands\n"
381  "after cancel <id> cancel the postponed command with given id\n";
382 }
383 
384 void AfterCommand::tabCompletion(vector<string>& tokens) const
385 {
386  if (tokens.size() == 2) {
387  static const char* const cmds[] = {
388  "time", "realtime", "idle", "frame", "break", "boot",
389  "machine_switch", "info", "cancel",
390  };
391  completeString(tokens, cmds);
392  }
393  // TODO : make more complete
394 }
395 
396 template<typename PRED> void AfterCommand::executeMatches(PRED pred)
397 {
398  // predicate should return false on matches
399  auto it = partition(afterCmds.begin(), afterCmds.end(), pred);
400  AfterCmds tmp(std::make_move_iterator(it),
401  std::make_move_iterator(afterCmds.end()));
402  afterCmds.erase(it, afterCmds.end());
403  for (auto& c : tmp) {
404  c->execute();
405  }
406 }
407 
408 template<EventType T> struct AfterEventPred {
409  bool operator()(const unique_ptr<AfterCmd>& x) const {
410  return !dynamic_cast<AfterEventCmd<T>*>(x.get());
411  }
412 };
413 template<EventType T> void AfterCommand::executeEvents()
414 {
415  executeMatches(AfterEventPred<T>());
416 }
417 
419  bool operator()(const unique_ptr<AfterCmd>& x) const {
420  if (auto* cmd = dynamic_cast<AfterRealTimeCmd*>(x.get())) {
421  if (cmd->hasExpired()) {
422  return false;
423  }
424  }
425  return true;
426  }
427 };
428 
430  bool operator()(const unique_ptr<AfterCmd>& x) const {
431  if (auto* cmd = dynamic_cast<AfterTimedCmd*>(x.get())) {
432  if (cmd->getTime() == 0.0) {
433  return false;
434  }
435  }
436  return true;
437  }
438 };
439 
442  : event(event_) {}
443  bool operator()(const unique_ptr<AfterCmd>& x) const {
444  if (auto* cmd = dynamic_cast<AfterInputEventCmd*>(x.get())) {
445  if (cmd->getEvent()->matches(*event)) return false;
446  }
447  return true;
448  }
450 };
451 
452 int AfterCommand::signalEvent(const std::shared_ptr<const Event>& event)
453 {
454  if (event->getType() == OPENMSX_FINISH_FRAME_EVENT) {
455  executeEvents<OPENMSX_FINISH_FRAME_EVENT>();
456  } else if (event->getType() == OPENMSX_BREAK_EVENT) {
457  executeEvents<OPENMSX_BREAK_EVENT>();
458  } else if (event->getType() == OPENMSX_BOOT_EVENT) {
459  executeEvents<OPENMSX_BOOT_EVENT>();
460  } else if (event->getType() == OPENMSX_QUIT_EVENT) {
461  executeEvents<OPENMSX_QUIT_EVENT>();
462  } else if (event->getType() == OPENMSX_MACHINE_LOADED_EVENT) {
463  executeEvents<OPENMSX_MACHINE_LOADED_EVENT>();
464  } else if (event->getType() == OPENMSX_AFTER_REALTIME_EVENT) {
465  executeMatches(AfterTimePred());
466  } else if (event->getType() == OPENMSX_AFTER_TIMED_EVENT) {
467  executeMatches(AfterEmuTimePred());
468  } else {
469  executeMatches(AfterInputEventPred(event));
470  for (auto& c : afterCmds) {
471  if (auto* cmd = dynamic_cast<AfterIdleCmd*>(c.get())) {
472  cmd->reschedule();
473  }
474  }
475  }
476  return 0;
477 }
478 
479 
480 // class AfterCmd
481 
482 unsigned AfterCmd::lastAfterId = 0;
483 
484 AfterCmd::AfterCmd(AfterCommand& afterCommand_, const TclObject& command_)
485  : afterCommand(afterCommand_), command(command_)
486 {
487  ostringstream str;
488  str << "after#" << ++lastAfterId;
489  id = str.str();
490 }
491 
493 {
494 }
495 
497 {
498  return command.getString();
499 }
500 
501 const string& AfterCmd::getId() const
502 {
503  return id;
504 }
505 
507 {
508  try {
509  command.executeCommand();
510  } catch (CommandException& e) {
512  "Error executing delayed command: " + e.getMessage());
513  }
514 }
515 
516 unique_ptr<AfterCmd> AfterCmd::removeSelf()
517 {
518  for (auto it = afterCommand.afterCmds.begin();
519  it != afterCommand.afterCmds.end(); ++it) {
520  if (it->get() == this) {
521  auto result = move(*it);
522  afterCommand.afterCmds.erase(it);
523  return result;
524  }
525  }
526  UNREACHABLE; return nullptr;
527 }
528 
529 
530 // class AfterTimedCmd
531 
533  Scheduler& scheduler,
534  AfterCommand& afterCommand,
535  const TclObject& command, double time_)
536  : AfterCmd(afterCommand, command)
537  , Schedulable(scheduler)
538  , time(time_)
539 
540 {
541  reschedule();
542 }
543 
545 {
546  return time;
547 }
548 
550 {
551  removeSyncPoint();
553 }
554 
555 void AfterTimedCmd::executeUntil(EmuTime::param /*time*/,
556  int /*userData*/)
557 {
558  time = 0.0; // execute on next event
559  afterCommand.eventDistributor.distributeEvent(
560  std::make_shared<SimpleEvent>(OPENMSX_AFTER_TIMED_EVENT));
561 }
562 
563 void AfterTimedCmd::schedulerDeleted()
564 {
565  removeSelf();
566 }
567 
568 
569 // class AfterTimeCmd
570 
572  Scheduler& scheduler,
573  AfterCommand& afterCommand,
574  const TclObject& command, double time)
575  : AfterTimedCmd(scheduler, afterCommand, command, time)
576 {
577 }
578 
579 string AfterTimeCmd::getType() const
580 {
581  return "time";
582 }
583 
584 
585 // class AfterIdleCmd
586 
588  Scheduler& scheduler,
589  AfterCommand& afterCommand,
590  const TclObject& command, double time)
591  : AfterTimedCmd(scheduler, afterCommand, command, time)
592 {
593 }
594 
595 string AfterIdleCmd::getType() const
596 {
597  return "idle";
598 }
599 
600 
601 // class AfterEventCmd
602 
603 template<EventType T>
605  AfterCommand& afterCommand, const TclObject& type_,
606  const TclObject& command)
607  : AfterCmd(afterCommand, command), type(type_.getString().str())
608 {
609 }
610 
611 template<EventType T>
613 {
614  return type;
615 }
616 
617 
618 // AfterInputEventCmd
619 
621  AfterCommand& afterCommand,
622  const AfterCommand::EventPtr& event_,
623  const TclObject& command)
624  : AfterCmd(afterCommand, command)
625  , event(event_)
626 {
627 }
628 
630 {
631  return event->toString();
632 }
633 
634 // class AfterRealTimeCmd
635 
637  AfterCommand& afterCommand,
638  const TclObject& command, double time)
639  : AfterCmd(afterCommand, command)
640  , expired(false)
641 {
642  schedule(unsigned(time * 1000000)); // micro seconds
643 }
644 
646 {
647  prepareDelete();
648 }
649 
651 {
652  return "realtime";
653 }
654 
655 bool AfterRealTimeCmd::alarm()
656 {
657  // this runs in a different thread, so we can't directly execute the
658  // command here
659  expired = true;
660  afterCommand.eventDistributor.distributeEvent(
661  std::make_shared<SimpleEvent>(OPENMSX_AFTER_REALTIME_EVENT));
662  return false; // don't repeat alarm
663 }
664 
666 {
667  return expired;
668 }
669 
670 } // namespace openmsx