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