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