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