openMSX
Reactor.cc
Go to the documentation of this file.
1 #include "Reactor.hh"
2 #include "CommandLineParser.hh"
3 #include "RTScheduler.hh"
4 #include "EventDistributor.hh"
6 #include "InputEventGenerator.hh"
7 #include "InputEvents.hh"
8 #include "DiskFactory.hh"
9 #include "DiskManipulator.hh"
10 #include "DiskChanger.hh"
11 #include "FilePool.hh"
12 #include "UserSettings.hh"
13 #include "RomDatabase.hh"
14 #include "TclCallbackMessages.hh"
15 #include "MSXMotherBoard.hh"
17 #include "Command.hh"
18 #include "AfterCommand.hh"
19 #include "MessageCommand.hh"
20 #include "CommandException.hh"
21 #include "GlobalCliComm.hh"
22 #include "InfoTopic.hh"
23 #include "Display.hh"
24 #include "Mixer.hh"
25 #include "AviRecorder.hh"
26 #include "GlobalSettings.hh"
27 #include "BooleanSetting.hh"
28 #include "EnumSetting.hh"
29 #include "TclObject.hh"
30 #include "HardwareConfig.hh"
31 #include "XMLElement.hh"
32 #include "XMLException.hh"
33 #include "FileContext.hh"
34 #include "FileException.hh"
35 #include "FileOperations.hh"
36 #include "ReadDir.hh"
37 #include "Thread.hh"
38 #include "Timer.hh"
39 #include "serialize.hh"
40 #include "openmsx.hh"
41 #include "checked_cast.hh"
42 #include "statp.hh"
43 #include "stl.hh"
44 #include "unreachable.hh"
45 #include "memory.hh"
46 #include "build-info.hh"
47 #include <cassert>
48 
49 using std::string;
50 using std::vector;
51 using std::make_shared;
52 
53 namespace openmsx {
54 
55 class QuitCommand final : public Command
56 {
57 public:
58  QuitCommand(CommandController& commandController, EventDistributor& distributor);
59  void execute(array_ref<TclObject> tokens, TclObject& result) override;
60  string help(const vector<string>& tokens) const override;
61 private:
62  EventDistributor& distributor;
63 };
64 
65 class MachineCommand final : public Command
66 {
67 public:
68  MachineCommand(CommandController& commandController, Reactor& reactor);
69  void execute(array_ref<TclObject> tokens, TclObject& result) override;
70  string help(const vector<string>& tokens) const override;
71  void tabCompletion(vector<string>& tokens) const override;
72 private:
73  Reactor& reactor;
74 };
75 
76 class TestMachineCommand final : public Command
77 {
78 public:
79  TestMachineCommand(CommandController& commandController, Reactor& reactor);
80  void execute(array_ref<TclObject> tokens, TclObject& result) override;
81  string help(const vector<string>& tokens) const override;
82  void tabCompletion(vector<string>& tokens) const override;
83 private:
84  Reactor& reactor;
85 };
86 
87 class CreateMachineCommand final : public Command
88 {
89 public:
90  CreateMachineCommand(CommandController& commandController, Reactor& reactor);
91  void execute(array_ref<TclObject> tokens, TclObject& result) override;
92  string help(const vector<string>& tokens) const override;
93 private:
94  Reactor& reactor;
95 };
96 
97 class DeleteMachineCommand final : public Command
98 {
99 public:
100  DeleteMachineCommand(CommandController& commandController, Reactor& reactor);
101  void execute(array_ref<TclObject> tokens, TclObject& result) override;
102  string help(const vector<string>& tokens) const override;
103  void tabCompletion(vector<string>& tokens) const override;
104 private:
105  Reactor& reactor;
106 };
107 
108 class ListMachinesCommand final : public Command
109 {
110 public:
111  ListMachinesCommand(CommandController& commandController, Reactor& reactor);
112  void execute(array_ref<TclObject> tokens, TclObject& result) override;
113  string help(const vector<string>& tokens) const override;
114 private:
115  Reactor& reactor;
116 };
117 
118 class ActivateMachineCommand final : public Command
119 {
120 public:
121  ActivateMachineCommand(CommandController& commandController, Reactor& reactor);
122  void execute(array_ref<TclObject> tokens, TclObject& result) override;
123  string help(const vector<string>& tokens) const override;
124  void tabCompletion(vector<string>& tokens) const override;
125 private:
126  Reactor& reactor;
127 };
128 
129 class StoreMachineCommand final : public Command
130 {
131 public:
132  StoreMachineCommand(CommandController& commandController, Reactor& reactor);
133  void execute(array_ref<TclObject> tokens, TclObject& result) override;
134  string help(const vector<string>& tokens) const override;
135  void tabCompletion(vector<string>& tokens) const override;
136 private:
137  Reactor& reactor;
138 };
139 
140 class RestoreMachineCommand final : public Command
141 {
142 public:
143  RestoreMachineCommand(CommandController& commandController, Reactor& reactor);
144  void execute(array_ref<TclObject> tokens, TclObject& result) override;
145  string help(const vector<string>& tokens) const override;
146  void tabCompletion(vector<string>& tokens) const override;
147 private:
148  Reactor& reactor;
149 };
150 
151 class ConfigInfo final : public InfoTopic
152 {
153 public:
154  ConfigInfo(InfoCommand& openMSXInfoCommand, const string& configName);
155  void execute(array_ref<TclObject> tokens,
156  TclObject& result) const override;
157  string help(const vector<string>& tokens) const override;
158  void tabCompletion(vector<string>& tokens) const override;
159 private:
160  const string configName;
161 };
162 
163 class RealTimeInfo final : public InfoTopic
164 {
165 public:
166  explicit RealTimeInfo(InfoCommand& openMSXInfoCommand);
167  void execute(array_ref<TclObject> tokens,
168  TclObject& result) const override;
169  string help(const vector<string>& tokens) const override;
170 private:
171  const uint64_t reference;
172 };
173 
174 
176  : activeBoard(nullptr)
177  , blockedCounter(0)
178  , paused(false)
179  , running(true)
180  , isInit(false)
181 {
182 #if UNIQUE_PTR_BUG
183  display = nullptr;
184 #endif
185 }
186 
188 {
189  rtScheduler = make_unique<RTScheduler>();
190  eventDistributor = make_unique<EventDistributor>(*this);
191  globalCliComm = make_unique<GlobalCliComm>();
192  globalCommandController = make_unique<GlobalCommandController>(
193  *eventDistributor, *globalCliComm, *this);
194  globalSettings = make_unique<GlobalSettings>(
195  *globalCommandController);
196  inputEventGenerator = make_unique<InputEventGenerator>(
197  *globalCommandController, *eventDistributor, *globalSettings);
198  mixer = make_unique<Mixer>(
199  *this, *globalCommandController);
200  diskFactory = make_unique<DiskFactory>(
201  *this);
202  diskManipulator = make_unique<DiskManipulator>(
203  *globalCommandController, *this);
204  virtualDrive = make_unique<DiskChanger>(
205  *this, "virtual_drive");
206  filePool = make_unique<FilePool>(*globalCommandController, *this);
207  userSettings = make_unique<UserSettings>(
208  *globalCommandController);
209  softwareDatabase = make_unique<RomDatabase>(
210  *globalCommandController, *globalCliComm);
211  afterCommand = make_unique<AfterCommand>(
212  *this, *eventDistributor, *globalCommandController);
213  quitCommand = make_unique<QuitCommand>(
214  *globalCommandController, *eventDistributor);
215  messageCommand = make_unique<MessageCommand>(
216  *globalCommandController);
217  machineCommand = make_unique<MachineCommand>(
218  *globalCommandController, *this);
219  testMachineCommand = make_unique<TestMachineCommand>(
220  *globalCommandController, *this);
221  createMachineCommand = make_unique<CreateMachineCommand>(
222  *globalCommandController, *this);
223  deleteMachineCommand = make_unique<DeleteMachineCommand>(
224  *globalCommandController, *this);
225  listMachinesCommand = make_unique<ListMachinesCommand>(
226  *globalCommandController, *this);
227  activateMachineCommand = make_unique<ActivateMachineCommand>(
228  *globalCommandController, *this);
229  storeMachineCommand = make_unique<StoreMachineCommand>(
230  *globalCommandController, *this);
231  restoreMachineCommand = make_unique<RestoreMachineCommand>(
232  *globalCommandController, *this);
233  aviRecordCommand = make_unique<AviRecorder>(*this);
234  extensionInfo = make_unique<ConfigInfo>(
235  getOpenMSXInfoCommand(), "extensions");
236  machineInfo = make_unique<ConfigInfo>(
237  getOpenMSXInfoCommand(), "machines");
238  realTimeInfo = make_unique<RealTimeInfo>(
240  tclCallbackMessages = make_unique<TclCallbackMessages>(
241  *globalCliComm, *globalCommandController);
242 
243  createMachineSetting();
244 
246 
247  eventDistributor->registerEventListener(OPENMSX_QUIT_EVENT, *this);
248  eventDistributor->registerEventListener(OPENMSX_FOCUS_EVENT, *this);
249  eventDistributor->registerEventListener(OPENMSX_DELETE_BOARDS, *this);
250  isInit = true;
251 }
252 
254 {
255  if (!isInit) return;
256  deleteBoard(activeBoard);
257 
258  eventDistributor->unregisterEventListener(OPENMSX_QUIT_EVENT, *this);
259  eventDistributor->unregisterEventListener(OPENMSX_FOCUS_EVENT, *this);
260  eventDistributor->unregisterEventListener(OPENMSX_DELETE_BOARDS, *this);
261 
263 }
264 
266 {
267  return *globalCliComm;
268 }
269 
271 {
273 }
274 
276 {
277  return *globalCommandController;
278 }
279 
281 {
282  return globalCommandController->getOpenMSXInfoCommand();
283 }
284 
285 vector<string> Reactor::getHwConfigs(string_ref type)
286 {
287  vector<string> result;
288  for (auto& p : systemFileContext().getPaths()) {
289  const auto& path = FileOperations::join(p, type);
290  ReadDir configsDir(path);
291  while (auto* entry = configsDir.getEntry()) {
292  string_ref name = entry->d_name;
293  const auto& fullname = FileOperations::join(path, name);
294  if (name.ends_with(".xml") &&
295  FileOperations::isRegularFile(fullname)) {
296  name.remove_suffix(4);
297  result.push_back(name.str());
298  } else if (FileOperations::isDirectory(fullname)) {
299  const auto& config = FileOperations::join(
300  fullname, "hardwareconfig.xml");
301  if (FileOperations::isRegularFile(config)) {
302  result.push_back(name.str());
303  }
304  }
305  }
306  }
307  // remove duplicates
308  sort(begin(result), end(result));
309  result.erase(unique(begin(result), end(result)), end(result));
310  return result;
311 }
312 
313 void Reactor::createMachineSetting()
314 {
315  EnumSetting<int>::Map machines; // int's are unique dummy values
316  int count = 1;
317  for (auto& name : getHwConfigs("machines")) {
318  machines.emplace_back(name, count++);
319  }
320  machines.emplace_back("C-BIOS_MSX2+", 0); // default machine
321 
322  machineSetting = make_unique<EnumSetting<int>>(
323  *globalCommandController, "default_machine",
324  "default machine (takes effect next time openMSX is started)",
325  0, std::move(machines));
326 }
327 
329 {
330  assert(Thread::isMainThread());
331  return activeBoard;
332 }
333 
334 string Reactor::getMachineID() const
335 {
336  return activeBoard ? activeBoard->getMachineID() : "";
337 }
338 
339 vector<string_ref> Reactor::getMachineIDs() const
340 {
341  vector<string_ref> result;
342  for (auto& b : boards) {
343  result.push_back(b->getMachineID());
344  }
345  return result;
346 }
347 
348 MSXMotherBoard& Reactor::getMachine(string_ref machineID) const
349 {
350  for (auto& b : boards) {
351  if (b->getMachineID() == machineID) {
352  return *b;
353  }
354  }
355  throw CommandException("No machine with ID: " + machineID);
356 }
357 
359 {
360  return make_unique<MSXMotherBoard>(*this);
361 }
362 
363 void Reactor::replaceBoard(MSXMotherBoard& oldBoard_, Board newBoard_)
364 {
365  assert(Thread::isMainThread());
366 
367  // Add new board.
368  auto* newBoard = newBoard_.get();
369  boards.push_back(move(newBoard_));
370 
371  // Lookup old board (it must be present).
372  auto it = find_if_unguarded(boards,
373  [&](Boards::value_type& b) { return b.get() == &oldBoard_; });
374 
375  // If the old board was the active board, then activate the new board
376  if (it->get() == activeBoard) {
377  switchBoard(newBoard);
378  }
379 
380  // Remove (=delete) the old board.
381  // Note that we don't use the 'garbageBoards' mechanism as used in
382  // deleteBoard(). This means oldBoard cannot be used anymore right
383  // after this method returns.
384  move_pop_back(boards, it);
385 }
386 
387 void Reactor::switchMachine(const string& machine)
388 {
389  if (!display) {
390 #if UNIQUE_PTR_BUG
391  display2 = make_unique<Display>(*this);
392  display = display2.get();
393 #else
394  display = make_unique<Display>(*this);
395 #endif
396  // TODO: Currently it is not possible to move this call into the
397  // constructor of Display because the call to createVideoSystem()
398  // indirectly calls Reactor.getDisplay().
399  display->createVideoSystem();
400  }
401 
402  // create+load new machine
403  // switch to new machine
404  // delete old active machine
405 
406  assert(Thread::isMainThread());
407  // Note: loadMachine can throw an exception and in that case the
408  // motherboard must be considered as not created at all.
409  auto newBoard_ = createEmptyMotherBoard();
410  auto* newBoard = newBoard_.get();
411  newBoard->loadMachine(machine);
412  boards.push_back(move(newBoard_));
413 
414  auto* oldBoard = activeBoard;
415  switchBoard(newBoard);
416  deleteBoard(oldBoard);
417 }
418 
419 void Reactor::switchBoard(MSXMotherBoard* newBoard)
420 {
421  assert(Thread::isMainThread());
422  assert(!newBoard ||
423  (any_of(begin(boards), end(boards),
424  [&](Boards::value_type& b) { return b.get() == newBoard; })));
425  assert(!activeBoard ||
426  (any_of(begin(boards), end(boards),
427  [&](Boards::value_type& b) { return b.get() == activeBoard; })));
428  if (activeBoard) {
429  activeBoard->activate(false);
430  }
431  {
432  // Don't hold the lock for longer than the actual switch.
433  // In the past we had a potential for deadlocks here, because
434  // (indirectly) the code below still acquires other locks.
435  std::lock_guard<std::mutex> lock(mbMutex);
436  activeBoard = newBoard;
437  }
438  eventDistributor->distributeEvent(
439  make_shared<SimpleEvent>(OPENMSX_MACHINE_LOADED_EVENT));
440  globalCliComm->update(CliComm::HARDWARE, getMachineID(), "select");
441  if (activeBoard) {
442  activeBoard->activate(true);
443  }
444 }
445 
446 void Reactor::deleteBoard(MSXMotherBoard* board)
447 {
448  // Note: pass 'board' by-value to keep the parameter from changing
449  // after the call to switchBoard(). switchBoard() changes the
450  // 'activeBoard' member variable, so the 'board' parameter would change
451  // if it were passed by reference to this method (AFAICS this only
452  // happens in ~Reactor()).
453  assert(Thread::isMainThread());
454  if (!board) return;
455 
456  if (board == activeBoard) {
457  // delete active board -> there is no active board anymore
458  switchBoard(nullptr);
459  }
460  auto it = rfind_if_unguarded(boards,
461  [&](Boards::value_type& b) { return b.get() == board; });
462  auto board_ = move(*it);
463  move_pop_back(boards, it);
464  // Don't immediately delete old boards because it's possible this
465  // routine is called via a code path that goes through the old
466  // board. Instead remember this board and delete it at a safe moment
467  // in time.
468  garbageBoards.push_back(move(board_));
469  eventDistributor->distributeEvent(
470  make_shared<SimpleEvent>(OPENMSX_DELETE_BOARDS));
471 }
472 
474 {
475  // Note: this method can get called from different threads
476  if (Thread::isMainThread()) {
477  // Don't take lock in main thread to avoid recursive locking.
478  if (activeBoard) {
479  activeBoard->exitCPULoopSync();
480  }
481  } else {
482  std::lock_guard<std::mutex> lock(mbMutex);
483  if (activeBoard) {
484  activeBoard->exitCPULoopAsync();
485  }
486  }
487 }
488 
490 {
491  auto& commandController = *globalCommandController;
492 
493  // execute init.tcl
494  try {
495  commandController.source(
496  preferSystemFileContext().resolve("init.tcl"));
497  } catch (FileException&) {
498  // no init.tcl, ignore
499  }
500 
501  // execute startup scripts
502  for (auto& s : parser.getStartupScripts()) {
503  try {
504  commandController.source(userFileContext().resolve(s));
505  } catch (FileException& e) {
506  throw FatalError("Couldn't execute script: " +
507  e.getMessage());
508  }
509  }
510 
511  // At this point openmsx is fully started, it's OK now to start
512  // accepting external commands
514 
515  // Run
516  if (parser.getParseStatus() == CommandLineParser::RUN) {
517  // don't use Tcl to power up the machine, we cannot pass
518  // exceptions through Tcl and ADVRAM might throw in its
519  // powerUp() method. Solution is to implement dependencies
520  // between devices so ADVRAM can check the error condition
521  // in its constructor
522  //commandController.executeCommand("set power on");
523  if (activeBoard) {
524  activeBoard->powerUp();
525  }
526  }
527 
528  while (running) {
529  eventDistributor->deliverEvents();
530  assert(garbageBoards.empty());
531  bool blocked = (blockedCounter > 0) || !activeBoard;
532  if (!blocked) blocked = !activeBoard->execute();
533  if (blocked) {
534  // At first sight a better alternative is to use the
535  // SDL_WaitEvent() function. Though when inspecting
536  // the implementation of that function, it turns out
537  // to also use a sleep/poll loop, with even shorter
538  // sleep periods as we use here. Maybe in future
539  // SDL implementations this will be improved.
540  eventDistributor->sleep(20 * 1000);
541  }
542  }
543 }
544 
545 void Reactor::unpause()
546 {
547  if (paused) {
548  paused = false;
549  globalCliComm->update(CliComm::STATUS, "paused", "false");
550  unblock();
551  }
552 }
553 
554 void Reactor::pause()
555 {
556  if (!paused) {
557  paused = true;
558  globalCliComm->update(CliComm::STATUS, "paused", "true");
559  block();
560  }
561 }
562 
564 {
565  ++blockedCounter;
566  enterMainLoop();
567  mixer->mute();
568 }
569 
571 {
572  --blockedCounter;
573  assert(blockedCounter >= 0);
574  mixer->unmute();
575 }
576 
577 
578 // Observer<Setting>
579 void Reactor::update(const Setting& setting)
580 {
581  auto& pauseSetting = getGlobalSettings().getPauseSetting();
582  if (&setting == &pauseSetting) {
583  if (pauseSetting.getBoolean()) {
584  pause();
585  } else {
586  unpause();
587  }
588  }
589 }
590 
591 // EventListener
592 int Reactor::signalEvent(const std::shared_ptr<const Event>& event)
593 {
594  auto type = event->getType();
595  if (type == OPENMSX_QUIT_EVENT) {
596  enterMainLoop();
597  running = false;
598  } else if (type == OPENMSX_FOCUS_EVENT) {
599 #if PLATFORM_ANDROID
600  // Android SDL port sends a (un)focus event when an app is put in background
601  // by the OS for whatever reason (like an incoming phone call) and all screen
602  // resources are taken away from the app.
603  // In such case the app is supposed to behave as a good citizen
604  // and minize its resource usage and related battery drain.
605  // The SDL Android port already takes care of halting the Java
606  // part of the sound processing. The Display class makes sure that it wont try
607  // to render anything to the (temporary missing) graphics resources but the
608  // main emulation should also be temporary stopped, in order to minimize CPU usage
609  auto& focusEvent = checked_cast<const FocusEvent&>(*event);
610  if (focusEvent.getGain()) {
611  unblock();
612  } else {
613  block();
614  }
615 #else
616  // On other platforms, the user may specify if openMSX should be
617  // halted on loss of focus.
618  if (!getGlobalSettings().getPauseOnLostFocusSetting().getBoolean()) {
619  return 0;
620  }
621  auto& focusEvent = checked_cast<const FocusEvent&>(*event);
622  if (focusEvent.getGain()) {
623  // gained focus
625  unpause();
626  }
627  } else {
628  // lost focus
629  pause();
630  }
631 #endif
632  } else if (type == OPENMSX_DELETE_BOARDS) {
633  // Doesn't really matter which one we delete, just that we do
634  // one per event.
635  assert(!garbageBoards.empty());
636  garbageBoards.pop_back();
637  } else {
638  UNREACHABLE; // we didn't subscribe to this event...
639  }
640  return 0;
641 }
642 
643 
644 // class QuitCommand
645 
647  EventDistributor& distributor_)
648  : Command(commandController_, "exit")
649  , distributor(distributor_)
650 {
651 }
652 
654 {
655  distributor.distributeEvent(make_shared<QuitEvent>());
656 }
657 
658 string QuitCommand::help(const vector<string>& /*tokens*/) const
659 {
660  return "Use this command to stop the emulator\n";
661 }
662 
663 
664 // class MachineCommand
665 
667  Reactor& reactor_)
668  : Command(commandController_, "machine")
669  , reactor(reactor_)
670 {
671 }
672 
674 {
675  switch (tokens.size()) {
676  case 1: // get current machine
677  // nothing
678  break;
679  case 2:
680  try {
681  reactor.switchMachine(tokens[1].getString().str());
682  } catch (MSXException& e) {
683  throw CommandException("Machine switching failed: " +
684  e.getMessage());
685  }
686  break;
687  default:
688  throw SyntaxError();
689  }
690  // Always return machineID (of current or of new machine).
691  result.setString(reactor.getMachineID());
692 }
693 
694 string MachineCommand::help(const vector<string>& /*tokens*/) const
695 {
696  return "Switch to a different MSX machine.";
697 }
698 
699 void MachineCommand::tabCompletion(vector<string>& tokens) const
700 {
701  completeString(tokens, Reactor::getHwConfigs("machines"));
702 }
703 
704 
705 // class TestMachineCommand
706 
708  Reactor& reactor_)
709  : Command(commandController_, "test_machine")
710  , reactor(reactor_)
711 {
712 }
713 
715  TclObject& result)
716 {
717  if (tokens.size() != 2) {
718  throw SyntaxError();
719  }
720  try {
721  MSXMotherBoard mb(reactor);
722  mb.loadMachine(tokens[1].getString().str());
723  } catch (MSXException& e) {
724  result.setString(e.getMessage()); // error
725  }
726 }
727 
728 string TestMachineCommand::help(const vector<string>& /*tokens*/) const
729 {
730  return "Test the configuration for the given machine. "
731  "Returns an error message explaining why the configuration is "
732  "invalid or an empty string in case of success.";
733 }
734 
735 void TestMachineCommand::tabCompletion(vector<string>& tokens) const
736 {
737  completeString(tokens, Reactor::getHwConfigs("machines"));
738 }
739 
740 
741 // class CreateMachineCommand
742 
744  CommandController& commandController_, Reactor& reactor_)
745  : Command(commandController_, "create_machine")
746  , reactor(reactor_)
747 {
748 }
749 
751 {
752  if (tokens.size() != 1) {
753  throw SyntaxError();
754  }
755  auto newBoard = reactor.createEmptyMotherBoard();
756  result.setString(newBoard->getMachineID());
757  reactor.boards.push_back(move(newBoard));
758 }
759 
760 string CreateMachineCommand::help(const vector<string>& /*tokens*/) const
761 {
762  return "Creates a new (empty) MSX machine. Returns the ID for the new "
763  "machine.\n"
764  "Use 'load_machine' to actually load a machine configuration "
765  "into this new machine.\n"
766  "The main reason create_machine and load_machine are two "
767  "separate commands is that sometimes you already want to know "
768  "the ID of the machine before load_machine starts emitting "
769  "events for this machine.";
770 }
771 
772 
773 // class DeleteMachineCommand
774 
776  CommandController& commandController_, Reactor& reactor_)
777  : Command(commandController_, "delete_machine")
778  , reactor(reactor_)
779 {
780 }
781 
783  TclObject& /*result*/)
784 {
785  if (tokens.size() != 2) {
786  throw SyntaxError();
787  }
788  reactor.deleteBoard(&reactor.getMachine(tokens[1].getString()));
789 }
790 
791 string DeleteMachineCommand::help(const vector<string>& /*tokens*/) const
792 {
793  return "Deletes the given MSX machine.";
794 }
795 
796 void DeleteMachineCommand::tabCompletion(vector<string>& tokens) const
797 {
798  completeString(tokens, reactor.getMachineIDs());
799 }
800 
801 
802 // class ListMachinesCommand
803 
805  CommandController& commandController_, Reactor& reactor_)
806  : Command(commandController_, "list_machines")
807  , reactor(reactor_)
808 {
809 }
810 
812  TclObject& result)
813 {
814  result.addListElements(reactor.getMachineIDs());
815 }
816 
817 string ListMachinesCommand::help(const vector<string>& /*tokens*/) const
818 {
819  return "Returns a list of all machine IDs.";
820 }
821 
822 
823 // class ActivateMachineCommand
824 
826  CommandController& commandController_, Reactor& reactor_)
827  : Command(commandController_, "activate_machine")
828  , reactor(reactor_)
829 {
830 }
831 
833  TclObject& result)
834 {
835  switch (tokens.size()) {
836  case 1:
837  break;
838  case 2: {
839  reactor.switchBoard(&reactor.getMachine(tokens[1].getString()));
840  break;
841  }
842  default:
843  throw SyntaxError();
844  }
845  result.setString(reactor.getMachineID());
846 }
847 
848 string ActivateMachineCommand::help(const vector<string>& /*tokens*/) const
849 {
850  return "Make another machine the active msx machine.\n"
851  "Or when invoked without arguments, query the ID of the "
852  "active msx machine.";
853 }
854 
855 void ActivateMachineCommand::tabCompletion(vector<string>& tokens) const
856 {
857  completeString(tokens, reactor.getMachineIDs());
858 }
859 
860 
861 // class StoreMachineCommand
862 
864  CommandController& commandController_, Reactor& reactor_)
865  : Command(commandController_, "store_machine")
866  , reactor(reactor_)
867 {
868 }
869 
871 {
872  string filename;
873  string_ref machineID;
874  switch (tokens.size()) {
875  case 1:
876  machineID = reactor.getMachineID();
877  filename = FileOperations::getNextNumberedFileName("savestates", "openmsxstate", ".xml.gz");
878  break;
879  case 2:
880  machineID = tokens[1].getString();
881  filename = FileOperations::getNextNumberedFileName("savestates", "openmsxstate", ".xml.gz");
882  break;
883  case 3:
884  machineID = tokens[1].getString();
885  filename = tokens[2].getString().str();
886  break;
887  default:
888  throw SyntaxError();
889  }
890 
891  auto& board = reactor.getMachine(machineID);
892 
893  XmlOutputArchive out(filename);
894  out.serialize("machine", board);
895  result.setString(filename);
896 }
897 
898 string StoreMachineCommand::help(const vector<string>& /*tokens*/) const
899 {
900  return
901  "store_machine Save state of current machine to file \"openmsxNNNN.xml.gz\"\n"
902  "store_machine machineID Save state of machine \"machineID\" to file \"openmsxNNNN.xml.gz\"\n"
903  "store_machine machineID <filename> Save state of machine \"machineID\" to indicated file\n"
904  "\n"
905  "This is a low-level command, the 'savestate' script is easier to use.";
906 }
907 
908 void StoreMachineCommand::tabCompletion(vector<string>& tokens) const
909 {
910  completeString(tokens, reactor.getMachineIDs());
911 }
912 
913 
914 // class RestoreMachineCommand
915 
917  CommandController& commandController_, Reactor& reactor_)
918  : Command(commandController_, "restore_machine")
919  , reactor(reactor_)
920 {
921 }
922 
924  TclObject& result)
925 {
926  auto newBoard = reactor.createEmptyMotherBoard();
927 
928  string filename;
929  switch (tokens.size()) {
930  case 1: {
931  // load last saved entry
932  struct stat st;
933  string dirName = FileOperations::getUserOpenMSXDir() + "/savestates/";
934  string lastEntry = "";
935  time_t lastTime = 0;
936  ReadDir dir(dirName);
937  while (dirent* d = dir.getEntry()) {
938  int res = stat((dirName + string(d->d_name)).c_str(), &st);
939  if ((res == 0) && S_ISREG(st.st_mode)) {
940  time_t modTime = st.st_mtime;
941  if (modTime > lastTime) {
942  lastEntry = string(d->d_name);
943  lastTime = modTime;
944  }
945  }
946  }
947  if (lastEntry == "") {
948  throw CommandException("Can't find last saved state.");
949  }
950  filename = dirName + lastEntry;
951  break;
952  }
953  case 2:
954  filename = tokens[1].getString().str();
955  break;
956  default:
957  throw SyntaxError();
958  }
959 
960  //std::cerr << "Loading " << filename << std::endl;
961  try {
962  XmlInputArchive in(filename);
963  in.serialize("machine", *newBoard);
964  } catch (XMLException& e) {
965  throw CommandException("Cannot load state, bad file format: " + e.getMessage());
966  } catch (MSXException& e) {
967  throw CommandException("Cannot load state: " + e.getMessage());
968  }
969 
970  // Savestate also contains stuff like the keyboard state at the moment
971  // the snapshot was created (this is required for reverse/replay). But
972  // now we want the MSX to see the actual host keyboard state.
973  newBoard->getStateChangeDistributor().stopReplay(newBoard->getCurrentTime());
974 
975  result.setString(newBoard->getMachineID());
976  reactor.boards.push_back(move(newBoard));
977 }
978 
979 string RestoreMachineCommand::help(const vector<string>& /*tokens*/) const
980 {
981  return
982  "restore_machine Load state from last saved state in default directory\n"
983  "restore_machine <filename> Load state from indicated file\n"
984  "\n"
985  "This is a low-level command, the 'loadstate' script is easier to use.";
986 }
987 
988 void RestoreMachineCommand::tabCompletion(vector<string>& tokens) const
989 {
990  // TODO: add the default files (state files in user's savestates dir)
992 }
993 
994 
995 // class ConfigInfo
996 
998  const string& configName_)
999  : InfoTopic(openMSXInfoCommand, configName_)
1000  , configName(configName_)
1001 {
1002 }
1003 
1005 {
1006  // TODO make meta info available through this info topic
1007  switch (tokens.size()) {
1008  case 2: {
1009  result.addListElements(Reactor::getHwConfigs(configName));
1010  break;
1011  }
1012  case 3: {
1013  try {
1014  auto config = HardwareConfig::loadConfig(
1015  configName, tokens[2].getString());
1016  if (auto* info = config.findChild("info")) {
1017  for (auto& i : info->getChildren()) {
1018  result.addListElement(i.getName());
1019  result.addListElement(i.getData());
1020  }
1021  }
1022  } catch (MSXException& e) {
1023  throw CommandException(
1024  "Couldn't get config info: " + e.getMessage());
1025  }
1026  break;
1027  }
1028  default:
1029  throw CommandException("Too many parameters");
1030  }
1031 }
1032 
1033 string ConfigInfo::help(const vector<string>& /*tokens*/) const
1034 {
1035  return "Shows a list of available " + configName + ", "
1036  "or get meta information about the selected item.\n";
1037 }
1038 
1039 void ConfigInfo::tabCompletion(vector<string>& tokens) const
1040 {
1041  completeString(tokens, Reactor::getHwConfigs(configName));
1042 }
1043 
1044 
1045 // class RealTimeInfo
1046 
1048  : InfoTopic(openMSXInfoCommand, "realtime")
1049  , reference(Timer::getTime())
1050 {
1051 }
1052 
1054  TclObject& result) const
1055 {
1056  auto delta = Timer::getTime() - reference;
1057  result.setDouble(delta / 1000000.0);
1058 }
1059 
1060 string RealTimeInfo::help(const vector<string>& /*tokens*/) const
1061 {
1062  return "Returns the time in seconds since openMSX was started.";
1063 }
1064 
1065 } // namespace openmsx
void execute(array_ref< TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:870
const std::string & getMachineID() const
void setDouble(double value)
Definition: TclObject.cc:47
ActivateMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:825
string help(const vector< string > &tokens) const override
Print help for this topic.
Definition: Reactor.cc:1033
Contains the main loop of openMSX.
Definition: Reactor.hh:61
bool isRegularFile(const Stat &st)
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: Reactor.cc:791
string_ref::const_iterator end(const string_ref &x)
Definition: string_ref.hh:167
GlobalCommandController & getGlobalCommandController()
Definition: Reactor.hh:78
size_type size() const
Definition: array_ref.hh:61
const Scripts & getStartupScripts() const
Send when a (new) machine configuration is loaded.
Definition: Event.hh:51
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: Reactor.cc:694
void serialize(const char *tag, T &t)
Definition: serialize.hh:530
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: Reactor.cc:898
std::string str() const
Definition: string_ref.cc:12
void execute(array_ref< TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:782
static std::vector< std::string > getHwConfigs(string_ref type)
Definition: Reactor.cc:285
FileContext preferSystemFileContext()
Definition: FileContext.cc:155
void execute(array_ref< TclObject > tokens, TclObject &result) const override
Show info on this topic.
Definition: Reactor.cc:1053
void replaceBoard(MSXMotherBoard &oldBoard, Board newBoard)
Definition: Reactor.cc:363
void execute(array_ref< TclObject > tokens, TclObject &result) const override
Show info on this topic.
Definition: Reactor.cc:1004
FileContext systemFileContext()
Definition: FileContext.cc:149
void execute(array_ref< TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:714
std::unique_ptr< MSXMotherBoard > Board
Definition: Reactor.hh:104
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: Reactor.cc:760
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:735
MachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:666
void distributeEvent(const EventPtr &event)
Schedule the given event for delivery.
string join(string_ref part1, string_ref part2)
Join two paths.
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:988
void remove_suffix(size_type n)
Definition: string_ref.hh:84
This class implements a subset of the proposal for std::string_ref (proposed for the next c++ standar...
Definition: string_ref.hh:18
BooleanSetting & getPauseSetting()
RestoreMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:916
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: Reactor.cc:979
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
Definition: stl.hh:192
CommandController & getCommandController()
Definition: Reactor.cc:275
void stopReplay(EmuTime::param time)
Explicitly stop replay.
void execute(array_ref< TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:673
static void completeFileName(std::vector< std::string > &tokens, const FileContext &context, const RANGE &extra)
Definition: Completer.hh:122
void attach(Observer< T > &observer)
Definition: Subject.hh:52
DeleteMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:775
QuitCommand(CommandController &commandController, EventDistributor &distributor)
Definition: Reactor.cc:646
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: Reactor.cc:817
void run(CommandLineParser &parser)
Main loop.
Definition: Reactor.cc:489
string help(const vector< string > &tokens) const override
Print help for this topic.
Definition: Reactor.cc:1060
GlobalSettings & getGlobalSettings()
Definition: Reactor.hh:97
bool execute()
Run emulation.
This class implements a subset of the proposal for std::array_ref (proposed for the next c++ standard...
Definition: array_ref.hh:19
MSXMotherBoard * getMotherBoard() const
Definition: Reactor.cc:328
bool ends_with(string_ref x) const
Definition: string_ref.cc:126
std::string loadMachine(const std::string &machine)
const std::string & getMessage() const
Definition: MSXException.hh:14
string getNextNumberedFileName(string_ref directory, string_ref prefix, string_ref extension)
Gets the next numbered file name with the specified prefix in the specified directory, with the specified extension.
void execute(array_ref< TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:653
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:699
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:908
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: Reactor.cc:728
GlobalCliComm & getGlobalCliComm()
Definition: Reactor.hh:77
InfoCommand & getOpenMSXInfoCommand()
Definition: Reactor.cc:280
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: Reactor.cc:658
void execute(array_ref< TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:923
static XMLElement loadConfig(string_ref type, string_ref name)
ConfigInfo(InfoCommand &openMSXInfoCommand, const string &configName)
Definition: Reactor.cc:997
std::string getMachineID() const
Definition: Reactor.cc:334
static void completeString(std::vector< std::string > &tokens, ITER begin, ITER end, bool caseSensitive=true)
Definition: Completer.hh:108
FileContext userFileContext(string_ref savePath)
Definition: FileContext.cc:161
void addListElement(string_ref element)
Definition: TclObject.cc:69
const string & getUserOpenMSXDir()
Get the openMSX dir in the user&#39;s home directory.
void unblock()
Definition: Reactor.cc:570
Delete old MSXMotherboards.
Definition: Event.hh:63
bool isDirectory(const Stat &st)
void setString(string_ref value)
Definition: TclObject.cc:14
RealTimeInfo(InfoCommand &openMSXInfoCommand)
Definition: Reactor.cc:1047
void activate(bool active)
TestMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:707
void enterMainLoop()
Definition: Reactor.cc:473
Simple wrapper around openmdir() / readdir() / closedir() functions.
Definition: ReadDir.hh:15
void detach(Observer< T > &observer)
Definition: Subject.hh:58
ParseStatus getParseStatus() const
CliComm & getCliComm()
Definition: Reactor.cc:265
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this topic.
Definition: Reactor.cc:1039
EmuTime::param getCurrentTime()
Convenience method: This is the same as getScheduler().getCurrentTime().
Board createEmptyMotherBoard()
Definition: Reactor.cc:358
ITER find_if_unguarded(ITER first, ITER last, PRED pred)
Faster alternative to &#39;find_if&#39; when it&#39;s guaranteed that the predicate will be true for at least one...
Definition: stl.hh:142
void switchMachine(const std::string &machine)
Definition: Reactor.cc:387
uint64_t getTime()
Get current (real) time in us.
Definition: Timer.cc:8
void tabCompletion(std::vector< std::string > &tokens) const override
Attempt tab completion for this command.
Definition: Command.cc:66
void execute(array_ref< TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:832
void execute(array_ref< TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:811
string_ref::const_iterator begin(const string_ref &x)
Definition: string_ref.hh:166
struct dirent * getEntry()
Get directory entry for next file.
Definition: ReadDir.cc:17
ListMachinesCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:804
void addListElements(ITER begin, ITER end)
Definition: TclObject.hh:156
void execute(array_ref< TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:750
void serialize(const char *tag, const T &t)
Definition: serialize.hh:435
StoreMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:863
auto rfind_if_unguarded(RANGE &range, PRED pred) -> decltype(std::begin(range))
Definition: stl.hh:174
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:855
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:796
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: Reactor.cc:848
CreateMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:743
StateChangeDistributor & getStateChangeDistributor()
static bool isMainThread()
Returns true when called from the main thread.
Definition: Thread.cc:19
Interpreter & getInterpreter()
Definition: Reactor.cc:270
void exitCPULoopAsync()
See CPU::exitCPULoopAsync().
#define UNREACHABLE
Definition: unreachable.hh:35