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