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 "InputEventFactory.hh"
7 #include "Reactor.hh"
8 #include "MSXMotherBoard.hh"
9 #include "RTSchedulable.hh"
10 #include "EmuTime.hh"
11 #include "CommandException.hh"
12 #include "TclObject.hh"
13 #include "stl.hh"
14 #include "unreachable.hh"
15 #include <algorithm>
16 #include <iterator>
17 #include <memory>
18 #include <sstream>
19 
20 using std::ostringstream;
21 using std::string;
22 using std::vector;
23 using std::unique_ptr;
24 using std::move;
25 
26 namespace openmsx {
27 
28 class AfterCmd
29 {
30 public:
31  virtual ~AfterCmd() = default;
32  string_view getCommand() const;
33  const string& getId() const;
34  virtual string getType() const = 0;
35  void execute();
36 protected:
38  const TclObject& command);
39  unique_ptr<AfterCmd> removeSelf();
40 
43  string id;
44  static unsigned lastAfterId;
45 };
46 
47 class AfterTimedCmd : public AfterCmd, private Schedulable
48 {
49 public:
50  double getTime() const;
51  void reschedule();
52 protected:
53  AfterTimedCmd(Scheduler& scheduler,
55  const TclObject& command, double time);
56 private:
57  void executeUntil(EmuTime::param time) override;
58  void schedulerDeleted() override;
59 
60  double time; // Zero when expired, otherwise the original duration (to
61  // be able to reschedule for 'after idle').
62 };
63 
64 class AfterTimeCmd final : public AfterTimedCmd
65 {
66 public:
67  AfterTimeCmd(Scheduler& scheduler,
69  const TclObject& command, double time);
70  string getType() const override;
71 };
72 
73 class AfterIdleCmd final : public AfterTimedCmd
74 {
75 public:
76  AfterIdleCmd(Scheduler& scheduler,
78  const TclObject& command, double time);
79  string getType() const override;
80 };
81 
82 template<EventType T>
83 class AfterEventCmd final : public AfterCmd
84 {
85 public:
87  const TclObject& type,
88  const TclObject& command);
89  string getType() const override;
90 private:
91  const string type;
92 };
93 
94 class AfterInputEventCmd final : public AfterCmd
95 {
96 public:
99  const TclObject& command);
100  string getType() const override;
101  AfterCommand::EventPtr getEvent() const { return event; }
102 private:
104 };
105 
106 class AfterRealTimeCmd final : public AfterCmd, private RTSchedulable
107 {
108 public:
110  const TclObject& command, double time);
111  string getType() const override;
112 
113 private:
114  void executeRT() override;
115 };
116 
117 
119  EventDistributor& eventDistributor_,
120  CommandController& commandController_)
121  : Command(commandController_, "after")
122  , reactor(reactor_)
123  , eventDistributor(eventDistributor_)
124 {
125  // TODO DETACHED <-> EMU types should be cleaned up
126  // (moved to event iso listener?)
127  eventDistributor.registerEventListener(
128  OPENMSX_KEY_UP_EVENT, *this);
129  eventDistributor.registerEventListener(
130  OPENMSX_KEY_DOWN_EVENT, *this);
131  eventDistributor.registerEventListener(
133  eventDistributor.registerEventListener(
135  eventDistributor.registerEventListener(
137  eventDistributor.registerEventListener(
139  eventDistributor.registerEventListener(
140  OPENMSX_JOY_HAT_EVENT, *this);
141  eventDistributor.registerEventListener(
143  eventDistributor.registerEventListener(
145  eventDistributor.registerEventListener(
147  eventDistributor.registerEventListener(
148  OPENMSX_BREAK_EVENT, *this);
149  eventDistributor.registerEventListener(
150  OPENMSX_QUIT_EVENT, *this);
151  eventDistributor.registerEventListener(
152  OPENMSX_BOOT_EVENT, *this);
153  eventDistributor.registerEventListener(
155  eventDistributor.registerEventListener(
157 }
158 
160 {
161  eventDistributor.unregisterEventListener(
163  eventDistributor.unregisterEventListener(
165  eventDistributor.unregisterEventListener(
166  OPENMSX_BOOT_EVENT, *this);
167  eventDistributor.unregisterEventListener(
168  OPENMSX_QUIT_EVENT, *this);
169  eventDistributor.unregisterEventListener(
170  OPENMSX_BREAK_EVENT, *this);
171  eventDistributor.unregisterEventListener(
173  eventDistributor.unregisterEventListener(
175  eventDistributor.unregisterEventListener(
177  eventDistributor.unregisterEventListener(
178  OPENMSX_JOY_HAT_EVENT, *this);
179  eventDistributor.unregisterEventListener(
181  eventDistributor.unregisterEventListener(
183  eventDistributor.unregisterEventListener(
185  eventDistributor.unregisterEventListener(
187  eventDistributor.unregisterEventListener(
188  OPENMSX_KEY_DOWN_EVENT, *this);
189  eventDistributor.unregisterEventListener(
190  OPENMSX_KEY_UP_EVENT, *this);
191 }
192 
194 {
195  if (tokens.size() < 2) {
196  throw CommandException("Missing argument");
197  }
198  string_view subCmd = tokens[1].getString();
199  if (subCmd == "time") {
200  afterTime(tokens, result);
201  } else if (subCmd == "realtime") {
202  afterRealTime(tokens, result);
203  } else if (subCmd == "idle") {
204  afterIdle(tokens, result);
205  } else if (subCmd == "frame") {
206  afterEvent<OPENMSX_FINISH_FRAME_EVENT>(tokens, result);
207  } else if (subCmd == "break") {
208  afterEvent<OPENMSX_BREAK_EVENT>(tokens, result);
209  } else if (subCmd == "quit") {
210  afterEvent<OPENMSX_QUIT_EVENT>(tokens, result);
211  } else if (subCmd == "boot") {
212  afterEvent<OPENMSX_BOOT_EVENT>(tokens, result);
213  } else if (subCmd == "machine_switch") {
214  afterEvent<OPENMSX_MACHINE_LOADED_EVENT>(tokens, result);
215  } else if (subCmd == "info") {
216  afterInfo(tokens, result);
217  } else if (subCmd == "cancel") {
218  afterCancel(tokens, result);
219  } else {
220  try {
221  // A valid integer?
222  int time = tokens[1].getInt(getInterpreter());
223  afterTclTime(time, tokens, result);
224  } catch (CommandException&) {
225  try {
226  // A valid event name?
227  afterInputEvent(
229  tokens[1], getInterpreter()),
230  tokens, result);
231  } catch (MSXException&) {
232  throw SyntaxError();
233  }
234  }
235  }
236 }
237 
238 static double getTime(Interpreter& interp, const TclObject& obj)
239 {
240  double time = obj.getDouble(interp);
241  if (time < 0) {
242  throw CommandException("Not a valid time specification");
243  }
244  return time;
245 }
246 
247 void AfterCommand::afterTime(array_ref<TclObject> tokens, TclObject& result)
248 {
249  if (tokens.size() != 4) {
250  throw SyntaxError();
251  }
252  MSXMotherBoard* motherBoard = reactor.getMotherBoard();
253  if (!motherBoard) return;
254  double time = getTime(getInterpreter(), tokens[2]);
255  auto cmd = std::make_unique<AfterTimeCmd>(
256  motherBoard->getScheduler(), *this, tokens[3], time);
257  result.setString(cmd->getId());
258  afterCmds.push_back(move(cmd));
259 }
260 
261 void AfterCommand::afterRealTime(array_ref<TclObject> tokens, TclObject& result)
262 {
263  if (tokens.size() != 4) {
264  throw SyntaxError();
265  }
266  double time = getTime(getInterpreter(), tokens[2]);
267  auto cmd = std::make_unique<AfterRealTimeCmd>(
268  reactor.getRTScheduler(), *this, tokens[3], time);
269  result.setString(cmd->getId());
270  afterCmds.push_back(move(cmd));
271 }
272 
273 void AfterCommand::afterTclTime(
274  int ms, array_ref<TclObject> tokens, TclObject& result)
275 {
276  TclObject command;
277  command.addListElements(std::begin(tokens) + 2, std::end(tokens));
278  auto cmd = std::make_unique<AfterRealTimeCmd>(
279  reactor.getRTScheduler(), *this, command, ms / 1000.0);
280  result.setString(cmd->getId());
281  afterCmds.push_back(move(cmd));
282 }
283 
284 template<EventType T>
285 void AfterCommand::afterEvent(array_ref<TclObject> tokens, TclObject& result)
286 {
287  if (tokens.size() != 3) {
288  throw SyntaxError();
289  }
290  auto cmd = std::make_unique<AfterEventCmd<T>>(
291  *this, tokens[1], tokens[2]);
292  result.setString(cmd->getId());
293  afterCmds.push_back(move(cmd));
294 }
295 
296 void AfterCommand::afterInputEvent(
297  const EventPtr& event, array_ref<TclObject> tokens, TclObject& result)
298 {
299  if (tokens.size() != 3) {
300  throw SyntaxError();
301  }
302  auto cmd = std::make_unique<AfterInputEventCmd>(
303  *this, event, tokens[2]);
304  result.setString(cmd->getId());
305  afterCmds.push_back(move(cmd));
306 }
307 
308 void AfterCommand::afterIdle(array_ref<TclObject> tokens, TclObject& result)
309 {
310  if (tokens.size() != 4) {
311  throw SyntaxError();
312  }
313  MSXMotherBoard* motherBoard = reactor.getMotherBoard();
314  if (!motherBoard) return;
315  double time = getTime(getInterpreter(), tokens[2]);
316  auto cmd = std::make_unique<AfterIdleCmd>(
317  motherBoard->getScheduler(), *this, tokens[3], time);
318  result.setString(cmd->getId());
319  afterCmds.push_back(move(cmd));
320 }
321 
322 void AfterCommand::afterInfo(array_ref<TclObject> /*tokens*/, TclObject& result)
323 {
324  ostringstream str;
325  for (auto& cmd : afterCmds) {
326  str << cmd->getId() << ": ";
327  str << cmd->getType() << ' ';
328  if (auto cmd2 = dynamic_cast<const AfterTimedCmd*>(cmd.get())) {
329  str.precision(3);
330  str << std::fixed << std::showpoint << cmd2->getTime() << ' ';
331  }
332  str << cmd->getCommand()
333  << '\n';
334  }
335  result.setString(str.str());
336 }
337 
338 void AfterCommand::afterCancel(array_ref<TclObject> tokens, TclObject& /*result*/)
339 {
340  if (tokens.size() < 3) {
341  throw SyntaxError();
342  }
343  if (tokens.size() == 3) {
344  auto id = tokens[2].getString();
345  auto it = find_if(begin(afterCmds), end(afterCmds),
346  [&](std::unique_ptr<AfterCmd>& e) { return e->getId() == id; });
347  if (it != end(afterCmds)) {
348  afterCmds.erase(it);
349  return;
350  }
351  }
352  TclObject command;
353  command.addListElements(std::begin(tokens) + 2, std::end(tokens));
354  string_view cmdStr = command.getString();
355  auto it = find_if(begin(afterCmds), end(afterCmds),
356  [&](std::unique_ptr<AfterCmd>& e) { return e->getCommand() == cmdStr; });
357  if (it != end(afterCmds)) {
358  afterCmds.erase(it);
359  // Tcl manual is not clear about this, but it seems
360  // there's only occurence of this command canceled.
361  // It's also not clear which of the (possibly) several
362  // matches is canceled.
363  return;
364  }
365  // It's not an error if no match is found
366 }
367 
368 string AfterCommand::help(const vector<string>& /*tokens*/) const
369 {
370  return "after time <seconds> <command> execute a command after some time (MSX time)\n"
371  "after realtime <seconds> <command> execute a command after some time (realtime)\n"
372  "after idle <seconds> <command> execute a command after some time being idle\n"
373  "after frame <command> execute a command after a new frame is drawn\n"
374  "after break <command> execute a command after a breakpoint is reached\n"
375  "after boot <command> execute a command after a (re)boot\n"
376  "after machine_switch <command> execute a command after a switch to a new machine\n"
377  "after info list all postponed commands\n"
378  "after cancel <id> cancel the postponed command with given id\n";
379 }
380 
381 void AfterCommand::tabCompletion(vector<string>& tokens) const
382 {
383  if (tokens.size() == 2) {
384  static const char* const cmds[] = {
385  "time", "realtime", "idle", "frame", "break", "boot",
386  "machine_switch", "info", "cancel",
387  };
388  completeString(tokens, cmds);
389  }
390  // TODO : make more complete
391 }
392 
393 // Execute the cmds for which the predicate returns true, and erase those from afterCmds.
394 template<typename PRED> void AfterCommand::executeMatches(PRED pred)
395 {
396  AfterCmds matches;
397  // Usually there are very few matches (typically even 0 or 1), so no
398  // need to reserve() space.
399  auto p = partition_copy_remove(begin(afterCmds), end(afterCmds),
400  std::back_inserter(matches), pred);
401  afterCmds.erase(p.second, end(afterCmds));
402  for (auto& c : matches) {
403  c->execute();
404  }
405 }
406 
407 template<EventType T> struct AfterEventPred {
408  bool operator()(const unique_ptr<AfterCmd>& x) const {
409  return dynamic_cast<AfterEventCmd<T>*>(x.get()) != nullptr;
410  }
411 };
412 template<EventType T> void AfterCommand::executeEvents()
413 {
414  executeMatches(AfterEventPred<T>());
415 }
416 
418  bool operator()(const unique_ptr<AfterCmd>& x) const {
419  if (auto* cmd = dynamic_cast<AfterTimedCmd*>(x.get())) {
420  if (cmd->getTime() == 0.0) {
421  return true;
422  }
423  }
424  return false;
425  }
426 };
427 
430  : event(std::move(event_)) {}
431  bool operator()(const unique_ptr<AfterCmd>& x) const {
432  if (auto* cmd = dynamic_cast<AfterInputEventCmd*>(x.get())) {
433  if (cmd->getEvent()->matches(*event)) return true;
434  }
435  return false;
436  }
438 };
439 
440 int AfterCommand::signalEvent(const std::shared_ptr<const Event>& event)
441 {
442  if (event->getType() == OPENMSX_FINISH_FRAME_EVENT) {
443  executeEvents<OPENMSX_FINISH_FRAME_EVENT>();
444  } else if (event->getType() == OPENMSX_BREAK_EVENT) {
445  executeEvents<OPENMSX_BREAK_EVENT>();
446  } else if (event->getType() == OPENMSX_BOOT_EVENT) {
447  executeEvents<OPENMSX_BOOT_EVENT>();
448  } else if (event->getType() == OPENMSX_QUIT_EVENT) {
449  executeEvents<OPENMSX_QUIT_EVENT>();
450  } else if (event->getType() == OPENMSX_MACHINE_LOADED_EVENT) {
451  executeEvents<OPENMSX_MACHINE_LOADED_EVENT>();
452  } else if (event->getType() == OPENMSX_AFTER_TIMED_EVENT) {
453  executeMatches(AfterEmuTimePred());
454  } else {
455  executeMatches(AfterInputEventPred(event));
456  for (auto& c : afterCmds) {
457  if (auto* cmd = dynamic_cast<AfterIdleCmd*>(c.get())) {
458  cmd->reschedule();
459  }
460  }
461  }
462  return 0;
463 }
464 
465 
466 // class AfterCmd
467 
468 unsigned AfterCmd::lastAfterId = 0;
469 
470 AfterCmd::AfterCmd(AfterCommand& afterCommand_, const TclObject& command_)
471  : afterCommand(afterCommand_), command(command_)
472 {
473  ostringstream str;
474  str << "after#" << ++lastAfterId;
475  id = str.str();
476 }
477 
479 {
480  return command.getString();
481 }
482 
483 const string& AfterCmd::getId() const
484 {
485  return id;
486 }
487 
489 {
490  try {
492  } catch (CommandException& e) {
494  "Error executing delayed command: ", e.getMessage());
495  }
496 }
497 
498 unique_ptr<AfterCmd> AfterCmd::removeSelf()
499 {
500  auto it = rfind_if_unguarded(afterCommand.afterCmds,
501  [&](std::unique_ptr<AfterCmd>& e) { return e.get() == this; });
502  auto result = move(*it);
503  afterCommand.afterCmds.erase(it);
504  return result;
505 }
506 
507 
508 // class AfterTimedCmd
509 
511  Scheduler& scheduler_,
512  AfterCommand& afterCommand_,
513  const TclObject& command_, double time_)
514  : AfterCmd(afterCommand_, command_)
515  , Schedulable(scheduler_)
516  , time(time_)
517 {
518  reschedule();
519 }
520 
522 {
523  return time;
524 }
525 
527 {
528  removeSyncPoint();
530 }
531 
532 void AfterTimedCmd::executeUntil(EmuTime::param /*time*/)
533 {
534  time = 0.0; // execute on next event
535  afterCommand.eventDistributor.distributeEvent(
536  std::make_shared<SimpleEvent>(OPENMSX_AFTER_TIMED_EVENT));
537 }
538 
539 void AfterTimedCmd::schedulerDeleted()
540 {
541  removeSelf();
542 }
543 
544 
545 // class AfterTimeCmd
546 
548  Scheduler& scheduler_,
549  AfterCommand& afterCommand_,
550  const TclObject& command_, double time_)
551  : AfterTimedCmd(scheduler_, afterCommand_, command_, time_)
552 {
553 }
554 
555 string AfterTimeCmd::getType() const
556 {
557  return "time";
558 }
559 
560 
561 // class AfterIdleCmd
562 
564  Scheduler& scheduler_,
565  AfterCommand& afterCommand_,
566  const TclObject& command_, double time_)
567  : AfterTimedCmd(scheduler_, afterCommand_, command_, time_)
568 {
569 }
570 
571 string AfterIdleCmd::getType() const
572 {
573  return "idle";
574 }
575 
576 
577 // class AfterEventCmd
578 
579 template<EventType T>
581  AfterCommand& afterCommand_, const TclObject& type_,
582  const TclObject& command_)
583  : AfterCmd(afterCommand_, command_), type(type_.getString().str())
584 {
585 }
586 
587 template<EventType T>
589 {
590  return type;
591 }
592 
593 
594 // AfterInputEventCmd
595 
597  AfterCommand& afterCommand_,
598  AfterCommand::EventPtr event_,
599  const TclObject& command_)
600  : AfterCmd(afterCommand_, command_)
601  , event(std::move(event_))
602 {
603 }
604 
606 {
607  return event->toString();
608 }
609 
610 // class AfterRealTimeCmd
611 
613  RTScheduler& rtScheduler, AfterCommand& afterCommand_,
614  const TclObject& command_, double time)
615  : AfterCmd(afterCommand_, command_)
616  , RTSchedulable(rtScheduler)
617 {
618  scheduleRT(uint64_t(time * 1e6)); // micro seconds
619 }
620 
622 {
623  return "realtime";
624 }
625 
626 void AfterRealTimeCmd::executeRT()
627 {
628  // Remove self before executing, but keep self alive till the end of
629  // this method. Otherwise execute could execute 'after cancel ..' and
630  // removeSelf() asserts that it can't find itself anymore.
631  auto self = removeSelf();
632  execute();
633 }
634 
635 } // namespace openmsx
AfterTimeCmd(Scheduler &scheduler, AfterCommand &afterCommand, const TclObject &command, double time)
string getType() const override
Contains the main loop of openMSX.
Definition: Reactor.hh:64
bool operator()(const unique_ptr< AfterCmd > &x) const
string getType() const override
string_view::const_iterator begin(const string_view &x)
Definition: string_view.hh:152
const std::string & getMessage() const &
Definition: MSXException.hh:23
Send when a (new) machine configuration is loaded.
Definition: Event.hh:51
bool operator()(const unique_ptr< AfterCmd > &x) const
void registerEventListener(EventType type, EventListener &listener, Priority priority=OTHER)
Registers a given object to receive certain events.
void unregisterEventListener(EventType type, EventListener &listener)
Unregisters a previously registered event listener.
bool operator()(const unique_ptr< AfterCmd > &x) const
EmuTime::param getCurrentTime() const
Convenience method: This is the same as getScheduler().getCurrentTime().
Definition: Schedulable.cc:49
string getType() const override
virtual string getType() const =0
string_view getCommand() const
string getType() const override
void distributeEvent(const EventPtr &event)
Schedule the given event for delivery.
STL namespace.
AfterCommand & afterCommand
Definition: AfterCommand.cc:41
string_view getString() const
Definition: TclObject.cc:139
std::shared_ptr< const Event > EventPtr
Definition: AfterCommand.hh:20
AfterInputEventPred(AfterCommand::EventPtr event_)
std::string help(const std::vector< std::string > &tokens) const override
Print help for this command.
MSXMotherBoard * getMotherBoard() const
Definition: Reactor.cc:348
AfterRealTimeCmd(RTScheduler &rtScheduler, AfterCommand &afterCommand, const TclObject &command, double time)
Send when an after-emutime command should be executed.
Definition: Event.hh:48
AfterEventCmd(AfterCommand &afterCommand, const TclObject &type, const TclObject &command)
RTScheduler & getRTScheduler()
Definition: Reactor.hh:78
Sent when VDP (V99x8 or V9990) reaches the end of a frame.
Definition: Event.hh:30
Every class that wants to get scheduled at some point must inherit from this class.
Definition: Schedulable.hh:33
This class implements a subset of the proposal for std::array_ref (proposed for the next c++ standard...
Definition: array_ref.hh:19
std::pair< OutputIt, ForwardIt > partition_copy_remove(ForwardIt first, ForwardIt last, OutputIt out_true, UnaryPredicate p)
This is like a combination of partition_copy() and remove().
Definition: stl.hh:227
string_view::const_iterator end(const string_view &x)
Definition: string_view.hh:153
double getTime() const
AfterCommand::EventPtr event
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
static void completeString(std::vector< std::string > &tokens, ITER begin, ITER end, bool caseSensitive=true)
Definition: Completer.hh:108
void setString(string_view value)
Definition: TclObject.cc:14
CommandController & getCommandController() const
Definition: Command.hh:23
AfterCommand::EventPtr getEvent() const
const string & getId() const
void scheduleRT(uint64_t delta)
size_type size() const
Definition: array_ref.hh:61
double getDouble(Interpreter &interp) const
Definition: TclObject.cc:129
Interpreter & getInterpreter() const
Definition: Command.cc:41
This class implements a (close approximation) of the std::string_view class.
Definition: string_view.hh:15
EventPtr createInputEvent(const TclObject &str, Interpreter &interp)
AfterCmd(AfterCommand &afterCommand, const TclObject &command)
AfterIdleCmd(Scheduler &scheduler, AfterCommand &afterCommand, const TclObject &command, double time)
virtual CliComm & getCliComm()=0
AfterCommand(Reactor &reactor, EventDistributor &eventDistributor, CommandController &commandController)
static unsigned lastAfterId
Definition: AfterCommand.cc:44
string getType() const override
void tabCompletion(std::vector< std::string > &tokens) const override
Attempt tab completion for this command.
AfterInputEventCmd(AfterCommand &afterCommand, AfterCommand::EventPtr event, const TclObject &command)
AfterTimedCmd(Scheduler &scheduler, AfterCommand &afterCommand, const TclObject &command, double time)
void setSyncPoint(EmuTime::param timestamp)
Definition: Schedulable.cc:23
TclObject executeCommand(Interpreter &interp, bool compile=false)
Interpret this TclObject as a command and execute it.
Definition: TclObject.cc:208
void addListElements(ITER begin, ITER end)
Definition: TclObject.hh:156
auto rfind_if_unguarded(RANGE &range, PRED pred) -> decltype(std::begin(range))
Definition: stl.hh:174
unique_ptr< AfterCmd > removeSelf()
void printWarning(string_view message)
Definition: CliComm.cc:20
void execute(array_ref< TclObject > tokens, TclObject &result) override
Execute this command.
virtual ~AfterCmd()=default