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 "view.hh"
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(
141  eventDistributor.registerEventListener(
142  OPENMSX_JOY_HAT_EVENT, *this);
143  eventDistributor.registerEventListener(
145  eventDistributor.registerEventListener(
147  eventDistributor.registerEventListener(
149  eventDistributor.registerEventListener(
150  OPENMSX_BREAK_EVENT, *this);
151  eventDistributor.registerEventListener(
152  OPENMSX_QUIT_EVENT, *this);
153  eventDistributor.registerEventListener(
154  OPENMSX_BOOT_EVENT, *this);
155  eventDistributor.registerEventListener(
157  eventDistributor.registerEventListener(
159 }
160 
162 {
163  eventDistributor.unregisterEventListener(
165  eventDistributor.unregisterEventListener(
167  eventDistributor.unregisterEventListener(
168  OPENMSX_BOOT_EVENT, *this);
169  eventDistributor.unregisterEventListener(
170  OPENMSX_QUIT_EVENT, *this);
171  eventDistributor.unregisterEventListener(
172  OPENMSX_BREAK_EVENT, *this);
173  eventDistributor.unregisterEventListener(
175  eventDistributor.unregisterEventListener(
177  eventDistributor.unregisterEventListener(
179  eventDistributor.unregisterEventListener(
180  OPENMSX_JOY_HAT_EVENT, *this);
181  eventDistributor.unregisterEventListener(
183  eventDistributor.unregisterEventListener(
185  eventDistributor.unregisterEventListener(
187  eventDistributor.unregisterEventListener(
189  eventDistributor.unregisterEventListener(
191  eventDistributor.unregisterEventListener(
192  OPENMSX_KEY_DOWN_EVENT, *this);
193  eventDistributor.unregisterEventListener(
194  OPENMSX_KEY_UP_EVENT, *this);
195 }
196 
198 {
199  if (tokens.size() < 2) {
200  throw CommandException("Missing argument");
201  }
202  string_view subCmd = tokens[1].getString();
203  if (subCmd == "time") {
204  afterTime(tokens, result);
205  } else if (subCmd == "realtime") {
206  afterRealTime(tokens, result);
207  } else if (subCmd == "idle") {
208  afterIdle(tokens, result);
209  } else if (subCmd == "frame") {
210  afterEvent<OPENMSX_FINISH_FRAME_EVENT>(tokens, result);
211  } else if (subCmd == "break") {
212  afterEvent<OPENMSX_BREAK_EVENT>(tokens, result);
213  } else if (subCmd == "quit") {
214  afterEvent<OPENMSX_QUIT_EVENT>(tokens, result);
215  } else if (subCmd == "boot") {
216  afterEvent<OPENMSX_BOOT_EVENT>(tokens, result);
217  } else if (subCmd == "machine_switch") {
218  afterEvent<OPENMSX_MACHINE_LOADED_EVENT>(tokens, result);
219  } else if (subCmd == "info") {
220  afterInfo(tokens, result);
221  } else if (subCmd == "cancel") {
222  afterCancel(tokens, result);
223  } else {
224  try {
225  // A valid integer?
226  int time = tokens[1].getInt(getInterpreter());
227  afterTclTime(time, tokens, result);
228  } catch (CommandException&) {
229  try {
230  // A valid event name?
231  afterInputEvent(
233  tokens[1], getInterpreter()),
234  tokens, result);
235  } catch (MSXException&) {
236  throw SyntaxError();
237  }
238  }
239  }
240 }
241 
242 static double getTime(Interpreter& interp, const TclObject& obj)
243 {
244  double time = obj.getDouble(interp);
245  if (time < 0) {
246  throw CommandException("Not a valid time specification");
247  }
248  return time;
249 }
250 
251 void AfterCommand::afterTime(span<const TclObject> tokens, TclObject& result)
252 {
253  checkNumArgs(tokens, 4, Prefix{2}, "seconds command");
254  MSXMotherBoard* motherBoard = reactor.getMotherBoard();
255  if (!motherBoard) return;
256  double time = getTime(getInterpreter(), tokens[2]);
257  auto cmd = std::make_unique<AfterTimeCmd>(
258  motherBoard->getScheduler(), *this, tokens[3], time);
259  result = cmd->getId();
260  afterCmds.push_back(move(cmd));
261 }
262 
263 void AfterCommand::afterRealTime(span<const TclObject> tokens, TclObject& result)
264 {
265  checkNumArgs(tokens, 4, Prefix{2}, "seconds command");
266  double time = getTime(getInterpreter(), tokens[2]);
267  auto cmd = std::make_unique<AfterRealTimeCmd>(
268  reactor.getRTScheduler(), *this, tokens[3], time);
269  result = cmd->getId();
270  afterCmds.push_back(move(cmd));
271 }
272 
273 void AfterCommand::afterTclTime(
274  int ms, span<const TclObject> tokens, TclObject& result)
275 {
276  TclObject command;
277  command.addListElements(view::drop(tokens, 2));
278  auto cmd = std::make_unique<AfterRealTimeCmd>(
279  reactor.getRTScheduler(), *this, command, ms / 1000.0);
280  result = cmd->getId();
281  afterCmds.push_back(move(cmd));
282 }
283 
284 template<EventType T>
285 void AfterCommand::afterEvent(span<const TclObject> tokens, TclObject& result)
286 {
287  checkNumArgs(tokens, 3, "command");
288  auto cmd = std::make_unique<AfterEventCmd<T>>(*this, tokens[1], tokens[2]);
289  result = cmd->getId();
290  afterCmds.push_back(move(cmd));
291 }
292 
293 void AfterCommand::afterInputEvent(
294  const EventPtr& event, span<const TclObject> tokens, TclObject& result)
295 {
296  checkNumArgs(tokens, 3, "command");
297  auto cmd = std::make_unique<AfterInputEventCmd>(*this, event, tokens[2]);
298  result = cmd->getId();
299  afterCmds.push_back(move(cmd));
300 }
301 
302 void AfterCommand::afterIdle(span<const TclObject> tokens, TclObject& result)
303 {
304  checkNumArgs(tokens, 4, Prefix{2}, "seconds command");
305  MSXMotherBoard* motherBoard = reactor.getMotherBoard();
306  if (!motherBoard) return;
307  double time = getTime(getInterpreter(), tokens[2]);
308  auto cmd = std::make_unique<AfterIdleCmd>(
309  motherBoard->getScheduler(), *this, tokens[3], time);
310  result = cmd->getId();
311  afterCmds.push_back(move(cmd));
312 }
313 
314 void AfterCommand::afterInfo(span<const TclObject> /*tokens*/, TclObject& result)
315 {
316  ostringstream str;
317  for (auto& cmd : afterCmds) {
318  str << cmd->getId() << ": ";
319  str << cmd->getType() << ' ';
320  if (auto cmd2 = dynamic_cast<const AfterTimedCmd*>(cmd.get())) {
321  str.precision(3);
322  str << std::fixed << std::showpoint << cmd2->getTime() << ' ';
323  }
324  str << cmd->getCommand()
325  << '\n';
326  }
327  result = str.str();
328 }
329 
330 void AfterCommand::afterCancel(span<const TclObject> tokens, TclObject& /*result*/)
331 {
332  checkNumArgs(tokens, AtLeast{3}, "id|command");
333  if (tokens.size() == 3) {
334  auto id = tokens[2].getString();
335  auto it = ranges::find_if(afterCmds,
336  [&](auto& e) { return e->getId() == id; });
337  if (it != end(afterCmds)) {
338  afterCmds.erase(it);
339  return;
340  }
341  }
342  TclObject command;
343  command.addListElements(view::drop(tokens, 2));
344  string_view cmdStr = command.getString();
345  auto it = ranges::find_if(afterCmds,
346  [&](auto& e) { return e->getCommand() == cmdStr; });
347  if (it != end(afterCmds)) {
348  afterCmds.erase(it);
349  // Tcl manual is not clear about this, but it seems
350  // there's only occurence of this command canceled.
351  // It's also not clear which of the (possibly) several
352  // matches is canceled.
353  return;
354  }
355  // It's not an error if no match is found
356 }
357 
358 string AfterCommand::help(const vector<string>& /*tokens*/) const
359 {
360  return "after time <seconds> <command> execute a command after some time (MSX time)\n"
361  "after realtime <seconds> <command> execute a command after some time (realtime)\n"
362  "after idle <seconds> <command> execute a command after some time being idle\n"
363  "after frame <command> execute a command after a new frame is drawn\n"
364  "after break <command> execute a command after a breakpoint is reached\n"
365  "after boot <command> execute a command after a (re)boot\n"
366  "after machine_switch <command> execute a command after a switch to a new machine\n"
367  "after info list all postponed commands\n"
368  "after cancel <id> cancel the postponed command with given id\n";
369 }
370 
371 void AfterCommand::tabCompletion(vector<string>& tokens) const
372 {
373  if (tokens.size() == 2) {
374  static const char* const cmds[] = {
375  "time", "realtime", "idle", "frame", "break", "boot",
376  "machine_switch", "info", "cancel",
377  };
378  completeString(tokens, cmds);
379  }
380  // TODO : make more complete
381 }
382 
383 // Execute the cmds for which the predicate returns true, and erase those from afterCmds.
384 template<typename PRED> void AfterCommand::executeMatches(PRED pred)
385 {
386  AfterCmds matches;
387  // Usually there are very few matches (typically even 0 or 1), so no
388  // need to reserve() space.
389  auto p = partition_copy_remove(afterCmds, std::back_inserter(matches), pred);
390  afterCmds.erase(p.second, end(afterCmds));
391  for (auto& c : matches) {
392  c->execute();
393  }
394 }
395 
396 template<EventType T> struct AfterEventPred {
397  bool operator()(const unique_ptr<AfterCmd>& x) const {
398  return dynamic_cast<AfterEventCmd<T>*>(x.get()) != nullptr;
399  }
400 };
401 template<EventType T> void AfterCommand::executeEvents()
402 {
403  executeMatches(AfterEventPred<T>());
404 }
405 
407  bool operator()(const unique_ptr<AfterCmd>& x) const {
408  if (auto* cmd = dynamic_cast<AfterTimedCmd*>(x.get())) {
409  if (cmd->getTime() == 0.0) {
410  return true;
411  }
412  }
413  return false;
414  }
415 };
416 
419  : event(std::move(event_)) {}
420  bool operator()(const unique_ptr<AfterCmd>& x) const {
421  if (auto* cmd = dynamic_cast<AfterInputEventCmd*>(x.get())) {
422  if (cmd->getEvent()->matches(*event)) return true;
423  }
424  return false;
425  }
427 };
428 
429 int AfterCommand::signalEvent(const std::shared_ptr<const Event>& event)
430 {
431  if (event->getType() == OPENMSX_FINISH_FRAME_EVENT) {
432  executeEvents<OPENMSX_FINISH_FRAME_EVENT>();
433  } else if (event->getType() == OPENMSX_BREAK_EVENT) {
434  executeEvents<OPENMSX_BREAK_EVENT>();
435  } else if (event->getType() == OPENMSX_BOOT_EVENT) {
436  executeEvents<OPENMSX_BOOT_EVENT>();
437  } else if (event->getType() == OPENMSX_QUIT_EVENT) {
438  executeEvents<OPENMSX_QUIT_EVENT>();
439  } else if (event->getType() == OPENMSX_MACHINE_LOADED_EVENT) {
440  executeEvents<OPENMSX_MACHINE_LOADED_EVENT>();
441  } else if (event->getType() == OPENMSX_AFTER_TIMED_EVENT) {
442  executeMatches(AfterEmuTimePred());
443  } else {
444  executeMatches(AfterInputEventPred(event));
445  for (auto& c : afterCmds) {
446  if (auto* cmd = dynamic_cast<AfterIdleCmd*>(c.get())) {
447  cmd->reschedule();
448  }
449  }
450  }
451  return 0;
452 }
453 
454 
455 // class AfterCmd
456 
457 unsigned AfterCmd::lastAfterId = 0;
458 
459 AfterCmd::AfterCmd(AfterCommand& afterCommand_, const TclObject& command_)
460  : afterCommand(afterCommand_), command(command_)
461 {
462  ostringstream str;
463  str << "after#" << ++lastAfterId;
464  id = str.str();
465 }
466 
468 {
469  return command.getString();
470 }
471 
472 const string& AfterCmd::getId() const
473 {
474  return id;
475 }
476 
478 {
479  try {
481  } catch (CommandException& e) {
483  "Error executing delayed command: ", e.getMessage());
484  }
485 }
486 
487 unique_ptr<AfterCmd> AfterCmd::removeSelf()
488 {
489  auto it = rfind_if_unguarded(afterCommand.afterCmds,
490  [&](std::unique_ptr<AfterCmd>& e) { return e.get() == this; });
491  auto result = move(*it);
492  afterCommand.afterCmds.erase(it);
493  return result;
494 }
495 
496 
497 // class AfterTimedCmd
498 
500  Scheduler& scheduler_,
501  AfterCommand& afterCommand_,
502  const TclObject& command_, double time_)
503  : AfterCmd(afterCommand_, command_)
504  , Schedulable(scheduler_)
505  , time(time_)
506 {
507  reschedule();
508 }
509 
511 {
512  return time;
513 }
514 
516 {
517  removeSyncPoint();
519 }
520 
521 void AfterTimedCmd::executeUntil(EmuTime::param /*time*/)
522 {
523  time = 0.0; // execute on next event
524  afterCommand.eventDistributor.distributeEvent(
525  std::make_shared<SimpleEvent>(OPENMSX_AFTER_TIMED_EVENT));
526 }
527 
528 void AfterTimedCmd::schedulerDeleted()
529 {
530  removeSelf();
531 }
532 
533 
534 // class AfterTimeCmd
535 
537  Scheduler& scheduler_,
538  AfterCommand& afterCommand_,
539  const TclObject& command_, double time_)
540  : AfterTimedCmd(scheduler_, afterCommand_, command_, time_)
541 {
542 }
543 
544 string AfterTimeCmd::getType() const
545 {
546  return "time";
547 }
548 
549 
550 // class AfterIdleCmd
551 
553  Scheduler& scheduler_,
554  AfterCommand& afterCommand_,
555  const TclObject& command_, double time_)
556  : AfterTimedCmd(scheduler_, afterCommand_, command_, time_)
557 {
558 }
559 
560 string AfterIdleCmd::getType() const
561 {
562  return "idle";
563 }
564 
565 
566 // class AfterEventCmd
567 
568 template<EventType T>
570  AfterCommand& afterCommand_, const TclObject& type_,
571  const TclObject& command_)
572  : AfterCmd(afterCommand_, command_), type(type_.getString().str())
573 {
574 }
575 
576 template<EventType T>
578 {
579  return type;
580 }
581 
582 
583 // AfterInputEventCmd
584 
586  AfterCommand& afterCommand_,
587  AfterCommand::EventPtr event_,
588  const TclObject& command_)
589  : AfterCmd(afterCommand_, command_)
590  , event(std::move(event_))
591 {
592 }
593 
595 {
596  return event->toString();
597 }
598 
599 // class AfterRealTimeCmd
600 
602  RTScheduler& rtScheduler, AfterCommand& afterCommand_,
603  const TclObject& command_, double time)
604  : AfterCmd(afterCommand_, command_)
605  , RTSchedulable(rtScheduler)
606 {
607  scheduleRT(uint64_t(time * 1e6)); // micro seconds
608 }
609 
611 {
612  return "realtime";
613 }
614 
615 void AfterRealTimeCmd::executeRT()
616 {
617  // Remove self before executing, but keep self alive till the end of
618  // this method. Otherwise execute could execute 'after cancel ..' and
619  // removeSelf() asserts that it can't find itself anymore.
620  auto self = removeSelf();
621  execute();
622 }
623 
624 } // 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
const std::string & getMessage() const &
Definition: MSXException.hh:23
Send when a (new) machine configuration is loaded.
Definition: Event.hh:58
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
void checkNumArgs(span< const TclObject > tokens, unsigned exactly, const char *errMessage) const
Definition: Completer.cc:180
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: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:55
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:37
Every class that wants to get scheduled at some point must inherit from this class.
Definition: Schedulable.hh:33
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:124
CommandController & getCommandController() const
Definition: Command.hh:25
AfterCommand::EventPtr getEvent() const
const string & getId() const
void scheduleRT(uint64_t delta)
double getDouble(Interpreter &interp) const
Definition: TclObject.cc:92
This class implements a (close approximation) of the std::string_view class.
Definition: string_view.hh:16
EventPtr createInputEvent(const TclObject &str, Interpreter &interp)
AfterCmd(AfterCommand &afterCommand, const TclObject &command)
AfterIdleCmd(Scheduler &scheduler, AfterCommand &afterCommand, const TclObject &command, double time)
Interpreter & getInterpreter() const final
Definition: Command.cc:41
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.
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:122
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:152
virtual ~AfterCmd()=default