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