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 "ranges.hh"
14 #include "stl.hh"
15 #include "unreachable.hh"
16 #include "view.hh"
17 #include <iterator>
18 #include <memory>
19 #include <sstream>
20 
21 using std::ostringstream;
22 using std::string;
23 using std::vector;
24 using std::unique_ptr;
25 using std::move;
26 
27 namespace openmsx {
28 
29 class AfterCmd
30 {
31 public:
32  virtual ~AfterCmd() = default;
33  string_view getCommand() const;
34  const string& getId() const;
35  virtual string getType() const = 0;
36  void execute();
37 protected:
39  const TclObject& command);
40  unique_ptr<AfterCmd> removeSelf();
41 
44  string id;
45  static unsigned lastAfterId;
46 };
47 
48 class AfterTimedCmd : public AfterCmd, private Schedulable
49 {
50 public:
51  double getTime() const;
52  void reschedule();
53 protected:
54  AfterTimedCmd(Scheduler& scheduler,
56  const TclObject& command, double time);
57 private:
58  void executeUntil(EmuTime::param time) override;
59  void schedulerDeleted() override;
60 
61  double time; // Zero when expired, otherwise the original duration (to
62  // be able to reschedule for 'after idle').
63 };
64 
65 class AfterTimeCmd final : public AfterTimedCmd
66 {
67 public:
68  AfterTimeCmd(Scheduler& scheduler,
70  const TclObject& command, double time);
71  string getType() const override;
72 };
73 
74 class AfterIdleCmd final : public AfterTimedCmd
75 {
76 public:
77  AfterIdleCmd(Scheduler& scheduler,
79  const TclObject& command, double time);
80  string getType() const override;
81 };
82 
83 template<EventType T>
84 class AfterEventCmd final : public AfterCmd
85 {
86 public:
88  const TclObject& type,
89  const TclObject& command);
90  string getType() const override;
91 private:
92  const string type;
93 };
94 
95 class AfterInputEventCmd final : public AfterCmd
96 {
97 public:
100  const TclObject& command);
101  string getType() const override;
102  AfterCommand::EventPtr getEvent() const { return event; }
103 private:
105 };
106 
107 class AfterRealTimeCmd final : public AfterCmd, private RTSchedulable
108 {
109 public:
111  const TclObject& command, double time);
112  string getType() const override;
113 
114 private:
115  void executeRT() override;
116 };
117 
118 
120  EventDistributor& eventDistributor_,
121  CommandController& commandController_)
122  : Command(commandController_, "after")
123  , reactor(reactor_)
124  , eventDistributor(eventDistributor_)
125 {
126  // TODO DETACHED <-> EMU types should be cleaned up
127  // (moved to event iso listener?)
128  eventDistributor.registerEventListener(
129  OPENMSX_KEY_UP_EVENT, *this);
130  eventDistributor.registerEventListener(
131  OPENMSX_KEY_DOWN_EVENT, *this);
132  eventDistributor.registerEventListener(
134  eventDistributor.registerEventListener(
136  eventDistributor.registerEventListener(
138  eventDistributor.registerEventListener(
140  eventDistributor.registerEventListener(
141  OPENMSX_JOY_HAT_EVENT, *this);
142  eventDistributor.registerEventListener(
144  eventDistributor.registerEventListener(
146  eventDistributor.registerEventListener(
148  eventDistributor.registerEventListener(
149  OPENMSX_BREAK_EVENT, *this);
150  eventDistributor.registerEventListener(
151  OPENMSX_QUIT_EVENT, *this);
152  eventDistributor.registerEventListener(
153  OPENMSX_BOOT_EVENT, *this);
154  eventDistributor.registerEventListener(
156  eventDistributor.registerEventListener(
158 }
159 
161 {
162  eventDistributor.unregisterEventListener(
164  eventDistributor.unregisterEventListener(
166  eventDistributor.unregisterEventListener(
167  OPENMSX_BOOT_EVENT, *this);
168  eventDistributor.unregisterEventListener(
169  OPENMSX_QUIT_EVENT, *this);
170  eventDistributor.unregisterEventListener(
171  OPENMSX_BREAK_EVENT, *this);
172  eventDistributor.unregisterEventListener(
174  eventDistributor.unregisterEventListener(
176  eventDistributor.unregisterEventListener(
178  eventDistributor.unregisterEventListener(
179  OPENMSX_JOY_HAT_EVENT, *this);
180  eventDistributor.unregisterEventListener(
182  eventDistributor.unregisterEventListener(
184  eventDistributor.unregisterEventListener(
186  eventDistributor.unregisterEventListener(
188  eventDistributor.unregisterEventListener(
189  OPENMSX_KEY_DOWN_EVENT, *this);
190  eventDistributor.unregisterEventListener(
191  OPENMSX_KEY_UP_EVENT, *this);
192 }
193 
195 {
196  if (tokens.size() < 2) {
197  throw CommandException("Missing argument");
198  }
199  string_view subCmd = tokens[1].getString();
200  if (subCmd == "time") {
201  afterTime(tokens, result);
202  } else if (subCmd == "realtime") {
203  afterRealTime(tokens, result);
204  } else if (subCmd == "idle") {
205  afterIdle(tokens, result);
206  } else if (subCmd == "frame") {
207  afterEvent<OPENMSX_FINISH_FRAME_EVENT>(tokens, result);
208  } else if (subCmd == "break") {
209  afterEvent<OPENMSX_BREAK_EVENT>(tokens, result);
210  } else if (subCmd == "quit") {
211  afterEvent<OPENMSX_QUIT_EVENT>(tokens, result);
212  } else if (subCmd == "boot") {
213  afterEvent<OPENMSX_BOOT_EVENT>(tokens, result);
214  } else if (subCmd == "machine_switch") {
215  afterEvent<OPENMSX_MACHINE_LOADED_EVENT>(tokens, result);
216  } else if (subCmd == "info") {
217  afterInfo(tokens, result);
218  } else if (subCmd == "cancel") {
219  afterCancel(tokens, result);
220  } else {
221  try {
222  // A valid integer?
223  int time = tokens[1].getInt(getInterpreter());
224  afterTclTime(time, tokens, result);
225  } catch (CommandException&) {
226  try {
227  // A valid event name?
228  afterInputEvent(
230  tokens[1], getInterpreter()),
231  tokens, result);
232  } catch (MSXException&) {
233  throw SyntaxError();
234  }
235  }
236  }
237 }
238 
239 static double getTime(Interpreter& interp, const TclObject& obj)
240 {
241  double time = obj.getDouble(interp);
242  if (time < 0) {
243  throw CommandException("Not a valid time specification");
244  }
245  return time;
246 }
247 
248 void AfterCommand::afterTime(span<const TclObject> tokens, TclObject& result)
249 {
250  if (tokens.size() != 4) {
251  throw SyntaxError();
252  }
253  MSXMotherBoard* motherBoard = reactor.getMotherBoard();
254  if (!motherBoard) return;
255  double time = getTime(getInterpreter(), tokens[2]);
256  auto cmd = std::make_unique<AfterTimeCmd>(
257  motherBoard->getScheduler(), *this, tokens[3], time);
258  result = cmd->getId();
259  afterCmds.push_back(move(cmd));
260 }
261 
262 void AfterCommand::afterRealTime(span<const TclObject> tokens, TclObject& result)
263 {
264  if (tokens.size() != 4) {
265  throw SyntaxError();
266  }
267  double time = getTime(getInterpreter(), tokens[2]);
268  auto cmd = std::make_unique<AfterRealTimeCmd>(
269  reactor.getRTScheduler(), *this, tokens[3], time);
270  result = cmd->getId();
271  afterCmds.push_back(move(cmd));
272 }
273 
274 void AfterCommand::afterTclTime(
275  int ms, span<const TclObject> tokens, TclObject& result)
276 {
277  TclObject command;
278  command.addListElements(view::drop(tokens, 2));
279  auto cmd = std::make_unique<AfterRealTimeCmd>(
280  reactor.getRTScheduler(), *this, command, ms / 1000.0);
281  result = cmd->getId();
282  afterCmds.push_back(move(cmd));
283 }
284 
285 template<EventType T>
286 void AfterCommand::afterEvent(span<const TclObject> tokens, TclObject& result)
287 {
288  if (tokens.size() != 3) {
289  throw SyntaxError();
290  }
291  auto cmd = std::make_unique<AfterEventCmd<T>>(
292  *this, tokens[1], tokens[2]);
293  result = cmd->getId();
294  afterCmds.push_back(move(cmd));
295 }
296 
297 void AfterCommand::afterInputEvent(
298  const EventPtr& event, span<const TclObject> tokens, TclObject& result)
299 {
300  if (tokens.size() != 3) {
301  throw SyntaxError();
302  }
303  auto cmd = std::make_unique<AfterInputEventCmd>(
304  *this, event, tokens[2]);
305  result = cmd->getId();
306  afterCmds.push_back(move(cmd));
307 }
308 
309 void AfterCommand::afterIdle(span<const TclObject> tokens, TclObject& result)
310 {
311  if (tokens.size() != 4) {
312  throw SyntaxError();
313  }
314  MSXMotherBoard* motherBoard = reactor.getMotherBoard();
315  if (!motherBoard) return;
316  double time = getTime(getInterpreter(), tokens[2]);
317  auto cmd = std::make_unique<AfterIdleCmd>(
318  motherBoard->getScheduler(), *this, tokens[3], time);
319  result = cmd->getId();
320  afterCmds.push_back(move(cmd));
321 }
322 
323 void AfterCommand::afterInfo(span<const TclObject> /*tokens*/, TclObject& result)
324 {
325  ostringstream str;
326  for (auto& cmd : afterCmds) {
327  str << cmd->getId() << ": ";
328  str << cmd->getType() << ' ';
329  if (auto cmd2 = dynamic_cast<const AfterTimedCmd*>(cmd.get())) {
330  str.precision(3);
331  str << std::fixed << std::showpoint << cmd2->getTime() << ' ';
332  }
333  str << cmd->getCommand()
334  << '\n';
335  }
336  result = str.str();
337 }
338 
339 void AfterCommand::afterCancel(span<const TclObject> tokens, TclObject& /*result*/)
340 {
341  if (tokens.size() < 3) {
342  throw SyntaxError();
343  }
344  if (tokens.size() == 3) {
345  auto id = tokens[2].getString();
346  auto it = ranges::find_if(afterCmds,
347  [&](auto& e) { return e->getId() == id; });
348  if (it != end(afterCmds)) {
349  afterCmds.erase(it);
350  return;
351  }
352  }
353  TclObject command;
354  command.addListElements(view::drop(tokens, 2));
355  string_view cmdStr = command.getString();
356  auto it = ranges::find_if(afterCmds,
357  [&](auto& e) { return e->getCommand() == cmdStr; });
358  if (it != end(afterCmds)) {
359  afterCmds.erase(it);
360  // Tcl manual is not clear about this, but it seems
361  // there's only occurence of this command canceled.
362  // It's also not clear which of the (possibly) several
363  // matches is canceled.
364  return;
365  }
366  // It's not an error if no match is found
367 }
368 
369 string AfterCommand::help(const vector<string>& /*tokens*/) const
370 {
371  return "after time <seconds> <command> execute a command after some time (MSX time)\n"
372  "after realtime <seconds> <command> execute a command after some time (realtime)\n"
373  "after idle <seconds> <command> execute a command after some time being idle\n"
374  "after frame <command> execute a command after a new frame is drawn\n"
375  "after break <command> execute a command after a breakpoint is reached\n"
376  "after boot <command> execute a command after a (re)boot\n"
377  "after machine_switch <command> execute a command after a switch to a new machine\n"
378  "after info list all postponed commands\n"
379  "after cancel <id> cancel the postponed command with given id\n";
380 }
381 
382 void AfterCommand::tabCompletion(vector<string>& tokens) const
383 {
384  if (tokens.size() == 2) {
385  static const char* const cmds[] = {
386  "time", "realtime", "idle", "frame", "break", "boot",
387  "machine_switch", "info", "cancel",
388  };
389  completeString(tokens, cmds);
390  }
391  // TODO : make more complete
392 }
393 
394 // Execute the cmds for which the predicate returns true, and erase those from afterCmds.
395 template<typename PRED> void AfterCommand::executeMatches(PRED pred)
396 {
397  AfterCmds matches;
398  // Usually there are very few matches (typically even 0 or 1), so no
399  // need to reserve() space.
400  auto p = partition_copy_remove(afterCmds, 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:66
bool operator()(const unique_ptr< AfterCmd > &x) const
string getType() const override
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
Definition: span.hh:34
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:42
string_view getString() const
Definition: TclObject.cc:102
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:342
AfterRealTimeCmd(RTScheduler &rtScheduler, AfterCommand &afterCommand, const TclObject &command, double time)
auto find_if(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:113
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:80
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:31
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:226
double getTime() const
AfterCommand::EventPtr event
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
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
CommandController & getCommandController() const
Definition: Command.hh:23
AfterCommand::EventPtr getEvent() const
const string & getId() const
void scheduleRT(uint64_t delta)
double getDouble(Interpreter &interp) const
Definition: TclObject.cc:92
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:45
string getType() const override
void tabCompletion(std::vector< std::string > &tokens) const override
Attempt tab completion for this command.
auto drop(Range &&range, size_t n)
Definition: view.hh:294
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
void addListElements(ITER first, ITER last)
Definition: TclObject.hh:109
TclObject executeCommand(Interpreter &interp, bool compile=false)
Interpret this TclObject as a command and execute it.
Definition: TclObject.cc:172
unique_ptr< AfterCmd > removeSelf()
auto rfind_if_unguarded(RANGE &range, PRED pred)
Definition: stl.hh:174
void printWarning(string_view message)
Definition: CliComm.cc:20
auto end(const string_view &x)
Definition: string_view.hh:153
virtual ~AfterCmd()=default