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