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