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