openMSX
Reactor.cc
Go to the documentation of this file.
1#include "Reactor.hh"
3#include "RTScheduler.hh"
4#include "EventDistributor.hh"
7#include "Event.hh"
8#include "DiskFactory.hh"
9#include "DiskManipulator.hh"
10#include "DiskChanger.hh"
11#include "FilePool.hh"
12#include "ImGuiManager.hh"
13#include "UserSettings.hh"
14#include "RomDatabase.hh"
15#include "RomInfo.hh"
17#include "MsxChar2Unicode.hh"
18#include "MSXMotherBoard.hh"
19#include "MSXPPI.hh"
21#include "Command.hh"
22#include "AfterCommand.hh"
23#include "MessageCommand.hh"
24#include "CommandException.hh"
25#include "GlobalCliComm.hh"
26#include "InfoTopic.hh"
27#include "Display.hh"
28#include "VideoSystem.hh"
29#include "Mixer.hh"
30#include "AviRecorder.hh"
31#include "GlobalSettings.hh"
32#include "BooleanSetting.hh"
33#include "EnumSetting.hh"
34#include "TclObject.hh"
35#include "HardwareConfig.hh"
36#include "XMLElement.hh"
37#include "XMLException.hh"
38#include "FileContext.hh"
39#include "FileException.hh"
40#include "FileOperations.hh"
41#include "foreach_file.hh"
42#include "Thread.hh"
43#include "Timer.hh"
44#include "narrow.hh"
45#include "ranges.hh"
46#include "serialize.hh"
47#include "stl.hh"
48#include "StringOp.hh"
49#include "unreachable.hh"
50#include "build-info.hh"
51#include <array>
52#include <cassert>
53#include <memory>
54
55using std::make_unique;
56using std::string;
57using std::string_view;
58using std::vector;
59
60namespace openmsx {
61
62// global variable to communicate the exit-code from the 'exit' command to main()
63int exitCode = 0;
64
65class ExitCommand final : public Command
66{
67public:
68 ExitCommand(CommandController& commandController, EventDistributor& distributor);
69 void execute(std::span<const TclObject> tokens, TclObject& result) override;
70 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
71private:
72 EventDistributor& distributor;
73};
74
75class MachineCommand final : public Command
76{
77public:
78 MachineCommand(CommandController& commandController, Reactor& reactor);
79 void execute(std::span<const TclObject> tokens, TclObject& result) override;
80 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
81 void tabCompletion(vector<string>& tokens) const override;
82private:
83 Reactor& reactor;
84};
85
86class TestMachineCommand final : public Command
87{
88public:
89 TestMachineCommand(CommandController& commandController, Reactor& reactor);
90 void execute(std::span<const TclObject> tokens, TclObject& result) override;
91 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
92 void tabCompletion(vector<string>& tokens) const override;
93private:
94 Reactor& reactor;
95};
96
97class CreateMachineCommand final : public Command
98{
99public:
100 CreateMachineCommand(CommandController& commandController, Reactor& reactor);
101 void execute(std::span<const TclObject> tokens, TclObject& result) override;
102 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
103private:
104 Reactor& reactor;
105};
106
107class DeleteMachineCommand final : public Command
108{
109public:
110 DeleteMachineCommand(CommandController& commandController, Reactor& reactor);
111 void execute(std::span<const TclObject> tokens, TclObject& result) override;
112 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
113 void tabCompletion(vector<string>& tokens) const override;
114private:
115 Reactor& reactor;
116};
117
118class ListMachinesCommand final : public Command
119{
120public:
121 ListMachinesCommand(CommandController& commandController, Reactor& reactor);
122 void execute(std::span<const TclObject> tokens, TclObject& result) override;
123 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
124private:
125 Reactor& reactor;
126};
127
128class ActivateMachineCommand final : public Command
129{
130public:
131 ActivateMachineCommand(CommandController& commandController, Reactor& reactor);
132 void execute(std::span<const TclObject> tokens, TclObject& result) override;
133 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
134 void tabCompletion(vector<string>& tokens) const override;
135private:
136 Reactor& reactor;
137};
138
139class StoreMachineCommand final : public Command
140{
141public:
142 StoreMachineCommand(CommandController& commandController, Reactor& reactor);
143 void execute(std::span<const TclObject> tokens, TclObject& result) override;
144 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
145 void tabCompletion(vector<string>& tokens) const override;
146private:
147 Reactor& reactor;
148};
149
150class RestoreMachineCommand final : public Command
151{
152public:
153 RestoreMachineCommand(CommandController& commandController, Reactor& reactor);
154 void execute(std::span<const TclObject> tokens, TclObject& result) override;
155 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
156 void tabCompletion(vector<string>& tokens) const override;
157private:
158 Reactor& reactor;
159};
160
161class GetClipboardCommand final : public Command
162{
163public:
164 GetClipboardCommand(CommandController& commandController, Reactor& reactor);
165 void execute(std::span<const TclObject> tokens, TclObject& result) override;
166 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
167private:
168 Reactor& reactor;
169};
170
171class SetClipboardCommand final : public Command
172{
173public:
174 SetClipboardCommand(CommandController& commandController, Reactor& reactor);
175 void execute(std::span<const TclObject> tokens, TclObject& result) override;
176 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
177private:
178 Reactor& reactor;
179};
180
181class ConfigInfo final : public InfoTopic
182{
183public:
184 ConfigInfo(InfoCommand& openMSXInfoCommand, const string& configName);
185 void execute(std::span<const TclObject> tokens,
186 TclObject& result) const override;
187 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
188 void tabCompletion(vector<string>& tokens) const override;
189private:
190 const string configName;
191};
192
193class RealTimeInfo final : public InfoTopic
194{
195public:
196 explicit RealTimeInfo(InfoCommand& openMSXInfoCommand);
197 void execute(std::span<const TclObject> tokens,
198 TclObject& result) const override;
199 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
200private:
201 const uint64_t reference;
202};
203
205{
206public:
207 SoftwareInfoTopic(InfoCommand& openMSXInfoCommand, Reactor& reactor);
208 void execute(std::span<const TclObject> tokens,
209 TclObject& result) const override;
210 [[nodiscard]] std::string help(std::span<const TclObject> tokens) const override;
211private:
212 Reactor& reactor;
213};
214
215
216Reactor::Reactor() = default;
217
219{
220 rtScheduler = make_unique<RTScheduler>();
221 eventDistributor = make_unique<EventDistributor>(*this);
222 globalCliComm = make_unique<GlobalCliComm>();
223 globalCommandController = make_unique<GlobalCommandController>(
224 *eventDistributor, *globalCliComm, *this);
225 globalSettings = make_unique<GlobalSettings>(
226 *globalCommandController);
227 inputEventGenerator = make_unique<InputEventGenerator>(
228 *globalCommandController, *eventDistributor, *globalSettings);
229 symbolManager = make_unique<SymbolManager>(
230 *globalCommandController);
231 imGuiManager = make_unique<ImGuiManager>(*this);
232 diskFactory = make_unique<DiskFactory>(
233 *this);
234 diskManipulator = make_unique<DiskManipulator>(
235 *globalCommandController, *this);
236 virtualDrive = make_unique<DiskChanger>(
237 *this, "virtual_drive");
238 filePool = make_unique<FilePool>(*globalCommandController, *this);
239 userSettings = make_unique<UserSettings>(
240 *globalCommandController);
241 afterCommand = make_unique<AfterCommand>(
242 *this, *eventDistributor, *globalCommandController);
243 exitCommand = make_unique<ExitCommand>(
244 *globalCommandController, *eventDistributor);
245 messageCommand = make_unique<MessageCommand>(
246 *globalCommandController);
247 machineCommand = make_unique<MachineCommand>(
248 *globalCommandController, *this);
249 testMachineCommand = make_unique<TestMachineCommand>(
250 *globalCommandController, *this);
251 createMachineCommand = make_unique<CreateMachineCommand>(
252 *globalCommandController, *this);
253 deleteMachineCommand = make_unique<DeleteMachineCommand>(
254 *globalCommandController, *this);
255 listMachinesCommand = make_unique<ListMachinesCommand>(
256 *globalCommandController, *this);
257 activateMachineCommand = make_unique<ActivateMachineCommand>(
258 *globalCommandController, *this);
259 storeMachineCommand = make_unique<StoreMachineCommand>(
260 *globalCommandController, *this);
261 restoreMachineCommand = make_unique<RestoreMachineCommand>(
262 *globalCommandController, *this);
263 getClipboardCommand = make_unique<GetClipboardCommand>(
264 *globalCommandController, *this);
265 setClipboardCommand = make_unique<SetClipboardCommand>(
266 *globalCommandController, *this);
267 aviRecordCommand = make_unique<AviRecorder>(*this);
268 extensionInfo = make_unique<ConfigInfo>(
269 getOpenMSXInfoCommand(), "extensions");
270 machineInfo = make_unique<ConfigInfo>(
271 getOpenMSXInfoCommand(), "machines");
272 realTimeInfo = make_unique<RealTimeInfo>(
274 softwareInfoTopic = make_unique<SoftwareInfoTopic>(
275 getOpenMSXInfoCommand(), *this);
276 tclCallbackMessages = make_unique<TclCallbackMessages>(
277 *globalCliComm, *globalCommandController);
278
279 createMachineSetting();
280
282
283 eventDistributor->registerEventListener(EventType::QUIT, *this);
284#if PLATFORM_ANDROID
285 eventDistributor->registerEventListener(EventType::WINDOW, *this);
286#endif
287 isInit = true;
288}
289
291{
292 if (!isInit) return;
293 deleteBoard(activeBoard);
294
295 eventDistributor->unregisterEventListener(EventType::QUIT, *this);
296#if PLATFORM_ANDROID
297 eventDistributor->unregisterEventListener(EventType::WINDOW, *this);
298#endif
299
301}
302
304{
305 if (!mixer) {
306 mixer = make_unique<Mixer>(*this, *globalCommandController);
307 }
308 return *mixer;
309}
310
312{
313 if (!softwareDatabase) {
314 softwareDatabase = make_unique<RomDatabase>(*globalCliComm);
315 }
316 return *softwareDatabase;
317}
318
320{
321 return *globalCliComm;
322}
323
325{
327}
328
330{
331 return *globalCommandController;
332}
333
335{
336 return globalCommandController->getOpenMSXInfoCommand();
337}
338
340{
341 return globalCommandController->getHotKey();
342}
343
344vector<string> Reactor::getHwConfigs(string_view type)
345{
346 vector<string> result;
347 for (const auto& p : systemFileContext().getPaths()) {
348 auto fileAction = [&](const std::string& /*path*/, std::string_view name) {
349 if (name.ends_with(".xml")) {
350 name.remove_suffix(4);
351 result.emplace_back(name);
352 }
353 };
354 auto dirAction = [&](std::string& path, std::string_view name) {
355 auto size = path.size();
356 path += "/hardwareconfig.xml";
358 result.emplace_back(name);
359 }
360 path.resize(size);
361 };
362 foreach_file_and_directory(FileOperations::join(p, type), fileAction, dirAction);
363 }
364 // remove duplicates
365 ranges::sort(result);
366 result.erase(ranges::unique(result), end(result));
367 return result;
368}
369
371{
372 // TODO cleanup this code. It should be easier to get a hold of the
373 // 'MsxChar2Unicode' object. Probably the 'Keyboard' class is not the
374 // right location to store it.
375 try {
376 if (MSXMotherBoard* board = getMotherBoard()) {
377 if (MSXPPI* ppi = dynamic_cast<MSXPPI*>(board->findDevice("ppi"))) {
378 return ppi->getKeyboard().getMsxChar2Unicode();
379 }
380 }
381 } catch (MSXException&) {
382 }
383 static const MsxChar2Unicode defaultMsxChars("MSXVID.TXT");
384 return defaultMsxChars;
385}
386
387
388void Reactor::createMachineSetting()
389{
390 auto names = getHwConfigs("machines");
391 EnumSetting<int>::Map machines; // int's are unique dummy values
392 machines.reserve(names.size() + 1);
393 int count = 1;
394 append(machines, view::transform(names,
395 [&](auto& name) { return EnumSettingBase::MapEntry(std::move(name), count++); }));
396 machines.emplace_back("C-BIOS_MSX2+", 0); // default machine
397
398 machineSetting = make_unique<EnumSetting<int>>(
399 *globalCommandController, "default_machine",
400 "default machine (takes effect next time openMSX is started)",
401 0, std::move(machines));
402}
403
405{
406 assert(Thread::isMainThread());
407 return activeBoard.get();
408}
409
410string_view Reactor::getMachineID() const
411{
412 return activeBoard ? activeBoard->getMachineID() : string_view{};
413}
414
415Reactor::Board Reactor::getMachine(string_view machineID) const
416{
417 if (auto it = ranges::find(boards, machineID, &MSXMotherBoard::getMachineID);
418 it != boards.end()) {
419 return *it;
420 }
421 throw CommandException("No machine with ID: ", machineID);
422}
423
425{
426 return std::make_shared<MSXMotherBoard>(*this);
427}
428
430{
431 assert(Thread::isMainThread());
432
433 // Add new board.
434 boards.push_back(newBoard);
435
436 // Lookup old board (it must be present).
437 auto it = find_unguarded(boards, &oldBoard_,
438 [](auto& b) { return b.get(); });
439
440 // If the old board was the active board, then activate the new board
441 if (*it == activeBoard) {
442 switchBoard(newBoard);
443 }
444
445 // Remove the old board.
446 move_pop_back(boards, it);
447}
448
449void Reactor::switchMachine(const string& machine)
450{
451 if (!display) {
452 display = make_unique<Display>(*this);
453 // TODO: Currently it is not possible to move this call into the
454 // constructor of Display because the call to createVideoSystem()
455 // indirectly calls Reactor.getDisplay().
456 display->createVideoSystem();
457 }
458
459 // create+load new machine
460 // switch to new machine
461 // delete old active machine
462
463 assert(Thread::isMainThread());
464 // Note: loadMachine can throw an exception and in that case the
465 // motherboard must be considered as not created at all.
466 auto newBoard = createEmptyMotherBoard();
467 newBoard->loadMachine(machine);
468 boards.push_back(newBoard);
469
470 auto oldBoard = activeBoard;
471 switchBoard(newBoard);
472 deleteBoard(oldBoard);
473}
474
475void Reactor::switchBoard(Board newBoard)
476{
477 assert(Thread::isMainThread());
478 assert(!newBoard || contains(boards, newBoard));
479 assert(!activeBoard || contains(boards, activeBoard));
480 if (activeBoard) {
481 activeBoard->activate(false);
482 }
483 {
484 // Don't hold the lock for longer than the actual switch.
485 // In the past we had a potential for deadlocks here, because
486 // (indirectly) the code below still acquires other locks.
487 std::lock_guard<std::mutex> lock(mbMutex);
488 activeBoard = newBoard;
489 }
490 eventDistributor->distributeEvent(MachineLoadedEvent());
491 globalCliComm->update(CliComm::HARDWARE, getMachineID(), "select");
492 if (activeBoard) {
493 activeBoard->activate(true);
494 }
495}
496
497void Reactor::deleteBoard(Board board)
498{
499 // Note: pass 'board' by-value to keep the parameter from changing
500 // after the call to switchBoard(). switchBoard() changes the
501 // 'activeBoard' member variable, so the 'board' parameter would change
502 // if it were passed by reference to this method (AFAICS this only
503 // happens in ~Reactor()).
504 assert(Thread::isMainThread());
505 if (!board) return;
506
507 if (board == activeBoard) {
508 // delete active board -> there is no active board anymore
509 switchBoard(nullptr);
510 }
511 auto it = rfind_unguarded(boards, board);
512 move_pop_back(boards, it);
513}
514
516{
517 // Note: this method can get called from different threads
518 if (Thread::isMainThread()) {
519 // Don't take lock in main thread to avoid recursive locking.
520 if (activeBoard) {
521 activeBoard->exitCPULoopSync();
522 }
523 } else {
524 std::lock_guard<std::mutex> lock(mbMutex);
525 if (activeBoard) {
526 activeBoard->exitCPULoopAsync();
527 }
528 }
529}
530
532{
533 auto& commandController = *globalCommandController;
534
535 // execute init.tcl
536 try {
537 commandController.source(
538 preferSystemFileContext().resolve("init.tcl"));
539 } catch (FileException&) {
540 // no init.tcl, ignore
541 }
542
543 // execute startup scripts
544 for (const auto& s : parser.getStartupScripts()) {
545 try {
546 commandController.source(userFileContext().resolve(s));
547 } catch (FileException& e) {
548 throw FatalError("Couldn't execute script: ",
549 e.getMessage());
550 }
551 }
552 for (const auto& cmd : parser.getStartupCommands()) {
553 try {
554 commandController.executeCommand(cmd);
555 } catch (CommandException& e) {
556 throw FatalError("Couldn't execute command: ", cmd,
557 '\n', e.getMessage());
558 }
559 }
560
561 fullyStarted = true;
562
563 // At this point openmsx is fully started, it's OK now to start
564 // accepting external commands
566
567 // ...and re-emit any postponed message callbacks now that the scripts
568 // are loaded
569 tclCallbackMessages->redoPostponedCallbacks();
570
571 // Run
572 if (parser.getParseStatus() == CommandLineParser::RUN) {
573 // don't use Tcl to power up the machine, we cannot pass
574 // exceptions through Tcl and ADVRAM might throw in its
575 // powerUp() method. Solution is to implement dependencies
576 // between devices so ADVRAM can check the error condition
577 // in its constructor
578 //commandController.executeCommand("set power on");
579 if (activeBoard) {
580 activeBoard->powerUp();
581 }
582 }
583
584 while (doOneIteration()) {
585 // nothing
586 }
587}
588
589bool Reactor::doOneIteration()
590{
591 eventDistributor->deliverEvents();
592 bool blocked = (blockedCounter > 0) || !activeBoard;
593 if (!blocked) {
594 // copy shared_ptr to keep Board alive (e.g. in case of Tcl
595 // callbacks)
596 auto copy = activeBoard;
597 blocked = !copy->execute();
598 }
599 if (blocked) {
600 // At first sight a better alternative is to use the
601 // SDL_WaitEvent() function. Though when inspecting
602 // the implementation of that function, it turns out
603 // to also use a sleep/poll loop, with even shorter
604 // sleep periods as we use here. Maybe in future
605 // SDL implementations this will be improved.
606 eventDistributor->sleep(20 * 1000);
607 }
608 return running;
609}
610
611void Reactor::unpause()
612{
613 if (paused) {
614 paused = false;
615 globalCliComm->update(CliComm::STATUS, "paused", "false");
616 unblock();
617 }
618}
619
620void Reactor::pause()
621{
622 if (!paused) {
623 paused = true;
624 globalCliComm->update(CliComm::STATUS, "paused", "true");
625 block();
626 }
627}
628
630{
631 ++blockedCounter;
633 getMixer().mute();
634}
635
637{
638 --blockedCounter;
639 assert(blockedCounter >= 0);
640 getMixer().unmute();
641}
642
643
644// Observer<Setting>
645void Reactor::update(const Setting& setting) noexcept
646{
647 auto& pauseSetting = getGlobalSettings().getPauseSetting();
648 if (&setting == &pauseSetting) {
649 if (pauseSetting.getBoolean()) {
650 pause();
651 } else {
652 unpause();
653 }
654 }
655}
656
657// EventListener
658int Reactor::signalEvent(const Event& event)
659{
660 std::visit(overloaded{
661 [&](const QuitEvent& /*e*/) {
663 running = false;
664 },
665 [&](const WindowEvent& e) {
666 (void)e;
667#if PLATFORM_ANDROID
668 if (e.isMainWindow()) {
669 // Android SDL port sends a (un)focus event when an app is put in background
670 // by the OS for whatever reason (like an incoming phone call) and all screen
671 // resources are taken away from the app.
672 // In such case the app is supposed to behave as a good citizen
673 // and minimize its resource usage and related battery drain.
674 // The SDL Android port already takes care of halting the Java
675 // part of the sound processing. The Display class makes sure that it wont try
676 // to render anything to the (temporary missing) graphics resources but the
677 // main emulation should also be temporary stopped, in order to minimize CPU usage
678 if (e.getSdlWindowEvent().type == SDL_WINDOWEVENT_FOCUS_GAINED) {
679 unblock();
680 } else if (e.getSdlWindowEvent().type == SDL_WINDOWEVENT_FOCUS_LOST) {
681 block();
682 }
683 }
684#endif
685 },
686 [](const EventBase /*e*/) {
687 UNREACHABLE; // we didn't subscribe to this event...
688 }
689 }, event);
690 return 0;
691}
692
693
694// class ExitCommand
695
697 EventDistributor& distributor_)
698 : Command(commandController_, "exit")
699 , distributor(distributor_)
700{
701}
702
703void ExitCommand::execute(std::span<const TclObject> tokens, TclObject& /*result*/)
704{
705 checkNumArgs(tokens, Between{1, 2}, Prefix{1}, "?exitcode?");
706 switch (tokens.size()) {
707 case 1:
708 exitCode = 0;
709 break;
710 case 2:
711 exitCode = tokens[1].getInt(getInterpreter());
712 break;
713 }
714 distributor.distributeEvent(QuitEvent());
715}
716
717string ExitCommand::help(std::span<const TclObject> /*tokens*/) const
718{
719 return "Use this command to stop the emulator.\n"
720 "Optionally you can pass an exit-code.\n";
721}
722
723
724// class MachineCommand
725
727 Reactor& reactor_)
728 : Command(commandController_, "machine")
729 , reactor(reactor_)
730{
731}
732
733void MachineCommand::execute(std::span<const TclObject> tokens, TclObject& result)
734{
735 checkNumArgs(tokens, Between{1, 2}, Prefix{1}, "?machinetype?");
736 switch (tokens.size()) {
737 case 1: // get current machine
738 // nothing
739 break;
740 case 2:
741 try {
742 reactor.switchMachine(string(tokens[1].getString()));
743 } catch (MSXException& e) {
744 throw CommandException("Machine switching failed: ",
745 e.getMessage());
746 }
747 break;
748 }
749 // Always return machineID (of current or of new machine).
750 result = reactor.getMachineID();
751}
752
753string MachineCommand::help(std::span<const TclObject> /*tokens*/) const
754{
755 return "Switch to a different MSX machine.";
756}
757
758void MachineCommand::tabCompletion(vector<string>& tokens) const
759{
760 completeString(tokens, Reactor::getHwConfigs("machines"));
761}
762
763
764// class TestMachineCommand
765
767 Reactor& reactor_)
768 : Command(commandController_, "test_machine")
769 , reactor(reactor_)
770{
771}
772
773void TestMachineCommand::execute(std::span<const TclObject> tokens,
774 TclObject& result)
775{
776 checkNumArgs(tokens, 2, "machinetype");
777 try {
778 MSXMotherBoard mb(reactor);
779 mb.loadMachine(string(tokens[1].getString()));
780 } catch (MSXException& e) {
781 result = e.getMessage(); // error
782 }
783}
784
785string TestMachineCommand::help(std::span<const TclObject> /*tokens*/) const
786{
787 return "Test the configuration for the given machine. "
788 "Returns an error message explaining why the configuration is "
789 "invalid or an empty string in case of success.";
790}
791
792void TestMachineCommand::tabCompletion(vector<string>& tokens) const
793{
794 completeString(tokens, Reactor::getHwConfigs("machines"));
795}
796
797
798// class CreateMachineCommand
799
801 CommandController& commandController_, Reactor& reactor_)
802 : Command(commandController_, "create_machine")
803 , reactor(reactor_)
804{
805}
806
807void CreateMachineCommand::execute(std::span<const TclObject> tokens, TclObject& result)
808{
809 checkNumArgs(tokens, 1, Prefix{1}, nullptr);
810 auto newBoard = reactor.createEmptyMotherBoard();
811 result = newBoard->getMachineID();
812 reactor.boards.push_back(std::move(newBoard));
813}
814
815string CreateMachineCommand::help(std::span<const TclObject> /*tokens*/) const
816{
817 return "Creates a new (empty) MSX machine. Returns the ID for the new "
818 "machine.\n"
819 "Use 'load_machine' to actually load a machine configuration "
820 "into this new machine.\n"
821 "The main reason create_machine and load_machine are two "
822 "separate commands is that sometimes you already want to know "
823 "the ID of the machine before load_machine starts emitting "
824 "events for this machine.";
825}
826
827
828// class DeleteMachineCommand
829
831 CommandController& commandController_, Reactor& reactor_)
832 : Command(commandController_, "delete_machine")
833 , reactor(reactor_)
834{
835}
836
837void DeleteMachineCommand::execute(std::span<const TclObject> tokens,
838 TclObject& /*result*/)
839{
840 checkNumArgs(tokens, 2, "id");
841 reactor.deleteBoard(reactor.getMachine(tokens[1].getString()));
842}
843
844string DeleteMachineCommand::help(std::span<const TclObject> /*tokens*/) const
845{
846 return "Deletes the given MSX machine.";
847}
848
849void DeleteMachineCommand::tabCompletion(vector<string>& tokens) const
850{
851 completeString(tokens, reactor.getMachineIDs());
852}
853
854
855// class ListMachinesCommand
856
858 CommandController& commandController_, Reactor& reactor_)
859 : Command(commandController_, "list_machines")
860 , reactor(reactor_)
861{
862}
863
864void ListMachinesCommand::execute(std::span<const TclObject> /*tokens*/,
865 TclObject& result)
866{
867 result.addListElements(reactor.getMachineIDs());
868}
869
870string ListMachinesCommand::help(std::span<const TclObject> /*tokens*/) const
871{
872 return "Returns a list of all machine IDs.";
873}
874
875
876// class ActivateMachineCommand
877
879 CommandController& commandController_, Reactor& reactor_)
880 : Command(commandController_, "activate_machine")
881 , reactor(reactor_)
882{
883}
884
885void ActivateMachineCommand::execute(std::span<const TclObject> tokens,
886 TclObject& result)
887{
888 checkNumArgs(tokens, Between{1, 2}, Prefix{1}, "id");
889 switch (tokens.size()) {
890 case 1:
891 break;
892 case 2:
893 reactor.switchBoard(reactor.getMachine(tokens[1].getString()));
894 break;
895 }
896 result = reactor.getMachineID();
897}
898
899string ActivateMachineCommand::help(std::span<const TclObject> /*tokens*/) const
900{
901 return "Make another machine the active msx machine.\n"
902 "Or when invoked without arguments, query the ID of the "
903 "active msx machine.";
904}
905
906void ActivateMachineCommand::tabCompletion(vector<string>& tokens) const
907{
908 completeString(tokens, reactor.getMachineIDs());
909}
910
911
912// class StoreMachineCommand
913
915 CommandController& commandController_, Reactor& reactor_)
916 : Command(commandController_, "store_machine")
917 , reactor(reactor_)
918{
919}
920
921void StoreMachineCommand::execute(std::span<const TclObject> tokens, TclObject& result)
922{
923 checkNumArgs(tokens, Between{1, 3}, Prefix{1}, "?id? ?filename?");
924 string filename;
925 string_view machineID;
926 switch (tokens.size()) {
927 case 1:
928 machineID = reactor.getMachineID();
929 filename = FileOperations::getNextNumberedFileName("savestates", "openmsxstate", ".xml.gz");
930 break;
931 case 2:
932 machineID = tokens[1].getString();
933 filename = FileOperations::getNextNumberedFileName("savestates", "openmsxstate", ".xml.gz");
934 break;
935 case 3:
936 machineID = tokens[1].getString();
937 filename = tokens[2].getString();
938 break;
939 }
940
941 auto& board = *reactor.getMachine(machineID);
942
943 XmlOutputArchive out(filename);
944 out.serialize("machine", board);
945 out.close();
946 result = filename;
947}
948
949string StoreMachineCommand::help(std::span<const TclObject> /*tokens*/) const
950{
951 return
952 "store_machine Save state of current machine to file \"openmsxNNNN.xml.gz\"\n"
953 "store_machine machineID Save state of machine \"machineID\" to file \"openmsxNNNN.xml.gz\"\n"
954 "store_machine machineID <filename> Save state of machine \"machineID\" to indicated file\n"
955 "\n"
956 "This is a low-level command, the 'savestate' script is easier to use.";
957}
958
959void StoreMachineCommand::tabCompletion(vector<string>& tokens) const
960{
961 completeString(tokens, reactor.getMachineIDs());
962}
963
964
965// class RestoreMachineCommand
966
968 CommandController& commandController_, Reactor& reactor_)
969 : Command(commandController_, "restore_machine")
970 , reactor(reactor_)
971{
972}
973
974void RestoreMachineCommand::execute(std::span<const TclObject> tokens,
975 TclObject& result)
976{
977 checkNumArgs(tokens, Between{1, 2}, Prefix{1}, "?filename?");
978 auto newBoard = reactor.createEmptyMotherBoard();
979
980 string filename;
981 switch (tokens.size()) {
982 case 1: {
983 // load last saved entry
984 string lastEntry;
985 time_t lastTime = 0;
987 FileOperations::getUserOpenMSXDir() + "/savestates",
988 [&](const string& path, const FileOperations::Stat& st) {
989 time_t modTime = st.st_mtime;
990 if (modTime > lastTime) {
991 filename = path;
992 lastTime = modTime;
993 }
994 });
995 if (filename.empty()) {
996 throw CommandException("Can't find last saved state.");
997 }
998 break;
999 }
1000 case 2:
1001 filename = FileOperations::expandTilde(string(tokens[1].getString()));
1002 break;
1003 }
1004
1005 //std::cerr << "Loading " << filename << '\n';
1006 try {
1007 XmlInputArchive in(filename);
1008 in.serialize("machine", *newBoard);
1009 } catch (XMLException& e) {
1010 throw CommandException("Cannot load state, bad file format: ",
1011 e.getMessage());
1012 } catch (MSXException& e) {
1013 throw CommandException("Cannot load state: ", e.getMessage());
1014 }
1015
1016 // Savestate also contains stuff like the keyboard state at the moment
1017 // the snapshot was created (this is required for reverse/replay). But
1018 // now we want the MSX to see the actual host keyboard state.
1019 newBoard->getStateChangeDistributor().stopReplay(newBoard->getCurrentTime());
1020
1021 result = newBoard->getMachineID();
1022 reactor.boards.push_back(std::move(newBoard));
1023}
1024
1025string RestoreMachineCommand::help(std::span<const TclObject> /*tokens*/) const
1026{
1027 return "restore_machine Load state from last saved state in default directory\n"
1028 "restore_machine <filename> Load state from indicated file\n"
1029 "\n"
1030 "This is a low-level command, the 'loadstate' script is easier to use.";
1031}
1032
1033void RestoreMachineCommand::tabCompletion(vector<string>& tokens) const
1034{
1035 // TODO: add the default files (state files in user's savestates dir)
1037}
1038
1039
1040// class GetClipboardCommand
1041
1043 CommandController& commandController_, Reactor& reactor_)
1044 : Command(commandController_, "get_clipboard_text")
1045 , reactor(reactor_)
1046{
1047 // Note: cannot yet call getReactor().getDisplay() (e.g. to cache it)
1048 // display may not yet be initialized.
1049}
1050
1051void GetClipboardCommand::execute(std::span<const TclObject> tokens, TclObject& result)
1052{
1053 checkNumArgs(tokens, 1, Prefix{1}, nullptr);
1054 result = reactor.getDisplay().getVideoSystem().getClipboardText();
1055}
1056
1057string GetClipboardCommand::help(std::span<const TclObject> /*tokens*/) const
1058{
1059 return "Returns the (text) content of the clipboard as a string.";
1060}
1061
1062
1063// class SetClipboardCommand
1064
1066 CommandController& commandController_, Reactor& reactor_)
1067 : Command(commandController_, "set_clipboard_text")
1068 , reactor(reactor_)
1069{
1070}
1071
1072void SetClipboardCommand::execute(std::span<const TclObject> tokens, TclObject& /*result*/)
1073{
1074 checkNumArgs(tokens, 2, "text");
1075 reactor.getDisplay().getVideoSystem().setClipboardText(tokens[1].getString());
1076}
1077
1078string SetClipboardCommand::help(std::span<const TclObject> /*tokens*/) const
1079{
1080 return "Send the given string to the clipboard.";
1081}
1082
1083
1084// class ConfigInfo
1085
1087 const string& configName_)
1088 : InfoTopic(openMSXInfoCommand, configName_)
1089 , configName(configName_)
1090{
1091}
1092
1093void ConfigInfo::execute(std::span<const TclObject> tokens, TclObject& result) const
1094{
1095 // TODO make meta info available through this info topic
1096 switch (tokens.size()) {
1097 case 2: {
1098 result.addListElements(Reactor::getHwConfigs(configName));
1099 break;
1100 }
1101 case 3: {
1102 try {
1103 std::array<char, 8192> allocBuffer; // tweak
1104 XMLDocument doc{allocBuffer.data(), sizeof(allocBuffer)};
1106 doc, configName, tokens[2].getString());
1107 if (const auto* info = doc.getRoot()->findChild("info")) {
1108 for (const auto& c : info->getChildren()) {
1109 result.addDictKeyValue(c.getName(), c.getData());
1110 }
1111 }
1112 } catch (MSXException& e) {
1113 throw CommandException(
1114 "Couldn't get config info: ", e.getMessage());
1115 }
1116 break;
1117 }
1118 default:
1119 throw CommandException("Too many parameters");
1120 }
1121}
1122
1123string ConfigInfo::help(std::span<const TclObject> /*tokens*/) const
1124{
1125 return strCat("Shows a list of available ", configName, ", "
1126 "or get meta information about the selected item.\n");
1127}
1128
1129void ConfigInfo::tabCompletion(vector<string>& tokens) const
1130{
1131 completeString(tokens, Reactor::getHwConfigs(configName));
1132}
1133
1134
1135// class RealTimeInfo
1136
1138 : InfoTopic(openMSXInfoCommand, "realtime")
1139 , reference(Timer::getTime())
1140{
1141}
1142
1143void RealTimeInfo::execute(std::span<const TclObject> /*tokens*/,
1144 TclObject& result) const
1145{
1146 auto delta = Timer::getTime() - reference;
1147 result = narrow_cast<double>(delta) * (1.0 / 1000000.0);
1148}
1149
1150string RealTimeInfo::help(std::span<const TclObject> /*tokens*/) const
1151{
1152 return "Returns the time in seconds since openMSX was started.";
1153}
1154
1155
1156// SoftwareInfoTopic
1157
1159 : InfoTopic(openMSXInfoCommand, "software")
1160 , reactor(reactor_)
1161{
1162}
1163
1165 std::span<const TclObject> tokens, TclObject& result) const
1166{
1167 if (tokens.size() != 3) {
1168 throw CommandException("Wrong number of parameters");
1169 }
1170
1171 Sha1Sum sha1sum(tokens[2].getString());
1172 auto& romDatabase = reactor.getSoftwareDatabase();
1173 const RomInfo* romInfo = romDatabase.fetchRomInfo(sha1sum);
1174 if (!romInfo) {
1175 // no match found
1176 throw CommandException(
1177 "Software with sha1sum ", sha1sum.toString(), " not found");
1178 }
1179
1180 const char* bufStart = romDatabase.getBufferStart();
1181 result.addDictKeyValues("title", romInfo->getTitle(bufStart),
1182 "year", romInfo->getYear(bufStart),
1183 "company", romInfo->getCompany(bufStart),
1184 "country", romInfo->getCountry(bufStart),
1185 "orig_type", romInfo->getOrigType(bufStart),
1186 "remark", romInfo->getRemark(bufStart),
1187 "original", romInfo->getOriginal(),
1188 "mapper_type_name", RomInfo::romTypeToName(romInfo->getRomType()),
1189 "genmsxid", romInfo->getGenMSXid());
1190}
1191
1192string SoftwareInfoTopic::help(std::span<const TclObject> /*tokens*/) const
1193{
1194 return "Returns information about the software "
1195 "given its sha1sum, in a paired list.";
1196}
1197
1198} // namespace openmsx
BaseSetting * setting
Definition: Interpreter.cc:28
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:885
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:899
ActivateMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:878
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:906
Interpreter & getInterpreter() const final
Definition: Command.cc:38
const auto & getStartupScripts() const
ParseStatus getParseStatus() const
const auto & getStartupCommands() const
static void completeFileName(std::vector< std::string > &tokens, const FileContext &context, const RANGE &extra)
Definition: Completer.hh:147
static void completeString(std::vector< std::string > &tokens, ITER begin, ITER end, bool caseSensitive=true)
Definition: Completer.hh:133
void checkNumArgs(std::span< const TclObject > tokens, unsigned exactly, const char *errMessage) const
Definition: Completer.cc:177
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this topic.
Definition: Reactor.cc:1129
string help(std::span< const TclObject > tokens) const override
Print help for this topic.
Definition: Reactor.cc:1123
ConfigInfo(InfoCommand &openMSXInfoCommand, const string &configName)
Definition: Reactor.cc:1086
void execute(std::span< const TclObject > tokens, TclObject &result) const override
Show info on this topic.
Definition: Reactor.cc:1093
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:815
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:807
CreateMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:800
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:837
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:849
DeleteMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:830
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:844
VideoSystem & getVideoSystem()
Definition: Display.cc:98
void distributeEvent(Event &&event)
Schedule the given event for delivery.
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:717
ExitCommand(CommandController &commandController, EventDistributor &distributor)
Definition: Reactor.cc:696
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:703
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:1051
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:1057
GetClipboardCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:1042
BooleanSetting & getPauseSetting()
static void loadConfig(XMLDocument &doc, std::string_view type, std::string_view name)
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:864
ListMachinesCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:857
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:870
std::string loadMachine(const std::string &machine)
std::string_view getMachineID() const
MachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:726
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:733
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:758
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:753
void mute()
This methods (un)mute the sound.
Definition: Mixer.cc:115
void unmute()
Definition: Mixer.cc:122
Contains the main loop of openMSX.
Definition: Reactor.hh:72
GlobalSettings & getGlobalSettings()
Definition: Reactor.hh:113
MSXMotherBoard * getMotherBoard() const
Definition: Reactor.cc:404
std::shared_ptr< MSXMotherBoard > Board
Definition: Reactor.hh:120
CommandController & getCommandController()
Definition: Reactor.cc:329
void enterMainLoop()
Definition: Reactor.cc:515
GlobalCommandController & getGlobalCommandController()
Definition: Reactor.hh:88
InfoCommand & getOpenMSXInfoCommand()
Definition: Reactor.cc:334
void switchMachine(const std::string &machine)
Definition: Reactor.cc:449
Display & getDisplay()
Definition: Reactor.hh:90
void unblock()
Definition: Reactor.cc:636
CliComm & getCliComm()
Definition: Reactor.cc:319
void run(CommandLineParser &parser)
Main loop.
Definition: Reactor.cc:531
GlobalCliComm & getGlobalCliComm()
Definition: Reactor.hh:87
Board createEmptyMotherBoard()
Definition: Reactor.cc:424
Interpreter & getInterpreter()
Definition: Reactor.cc:324
const HotKey & getHotKey() const
Definition: Reactor.cc:339
void replaceBoard(MSXMotherBoard &oldBoard, Board newBoard)
Definition: Reactor.cc:429
std::string_view getMachineID() const
Definition: Reactor.cc:410
Mixer & getMixer()
Definition: Reactor.cc:303
const MsxChar2Unicode & getMsxChar2Unicode() const
Definition: Reactor.cc:370
static std::vector< std::string > getHwConfigs(std::string_view type)
Definition: Reactor.cc:344
RomDatabase & getSoftwareDatabase()
Definition: Reactor.cc:311
Board getMachine(std::string_view machineID) const
Definition: Reactor.cc:415
auto getMachineIDs() const
Definition: Reactor.hh:127
RealTimeInfo(InfoCommand &openMSXInfoCommand)
Definition: Reactor.cc:1137
string help(std::span< const TclObject > tokens) const override
Print help for this topic.
Definition: Reactor.cc:1150
void execute(std::span< const TclObject > tokens, TclObject &result) const override
Show info on this topic.
Definition: Reactor.cc:1143
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:1025
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:1033
RestoreMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:967
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:974
std::string_view getYear(const char *buf) const
Definition: RomInfo.hh:46
bool getOriginal() const
Definition: RomInfo.hh:62
unsigned getGenMSXid() const
Definition: RomInfo.hh:63
std::string_view getTitle(const char *buf) const
Definition: RomInfo.hh:43
std::string_view getCompany(const char *buf) const
Definition: RomInfo.hh:49
std::string_view getRemark(const char *buf) const
Definition: RomInfo.hh:58
static std::string_view romTypeToName(RomType type)
Definition: RomInfo.cc:187
std::string_view getCountry(const char *buf) const
Definition: RomInfo.hh:52
RomType getRomType() const
Definition: RomInfo.hh:61
std::string_view getOrigType(const char *buf) const
Definition: RomInfo.hh:55
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:1072
SetClipboardCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:1065
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:1078
This class represents the result of a sha1 calculation (a 160-bit value).
Definition: sha1.hh:23
std::string toString() const
Definition: utils/sha1.cc:230
std::string help(std::span< const TclObject > tokens) const override
Print help for this topic.
Definition: Reactor.cc:1192
void execute(std::span< const TclObject > tokens, TclObject &result) const override
Show info on this topic.
Definition: Reactor.cc:1164
SoftwareInfoTopic(InfoCommand &openMSXInfoCommand, Reactor &reactor)
Definition: Reactor.cc:1158
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:959
StoreMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:914
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:949
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:921
void detach(Observer< T > &observer)
Definition: Subject.hh:56
void attach(Observer< T > &observer)
Definition: Subject.hh:50
void addListElements(ITER first, ITER last)
Definition: TclObject.hh:128
void addDictKeyValue(const Key &key, const Value &value)
Definition: TclObject.hh:141
void addDictKeyValues(Args &&... args)
Definition: TclObject.hh:144
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:792
TestMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:766
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:773
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:785
virtual void setClipboardText(zstring_view text)=0
virtual std::string getClipboardText()=0
ALWAYS_INLINE void serialize(const char *tag, T &t, Args &&...args)
Definition: serialize.hh:970
ALWAYS_INLINE void serialize(const char *tag, const T &t, Args &&...args)
Definition: serialize.hh:891
constexpr double e
Definition: Math.hh:21
string expandTilde(string path)
Expand the '~' character to the users home directory.
string getNextNumberedFileName(string_view directory, string_view prefix, string_view extension, bool addSeparator)
Gets the next numbered file name with the specified prefix in the specified directory,...
bool isRegularFile(const Stat &st)
const string & getUserOpenMSXDir()
Get the openMSX dir in the user's home directory.
string join(string_view part1, string_view part2)
Join two paths.
bool isMainThread()
Returns true when called from the main thread.
Definition: Thread.cc:15
uint64_t getTime()
Get current (real) time in us.
Definition: Timer.cc:7
This file implemented 3 utility functions:
Definition: Autofire.cc:9
const FileContext & systemFileContext()
Definition: FileContext.cc:155
std::variant< KeyUpEvent, KeyDownEvent, MouseMotionEvent, MouseButtonUpEvent, MouseButtonDownEvent, MouseWheelEvent, JoystickAxisMotionEvent, JoystickHatEvent, JoystickButtonUpEvent, JoystickButtonDownEvent, OsdControlReleaseEvent, OsdControlPressEvent, WindowEvent, TextEvent, FileDropEvent, QuitEvent, FinishFrameEvent, CliCommandEvent, GroupEvent, BootEvent, FrameDrawnEvent, BreakEvent, SwitchRendererEvent, TakeReverseSnapshotEvent, AfterTimedEvent, MachineLoadedEvent, MachineActivatedEvent, MachineDeactivatedEvent, MidiInReaderEvent, MidiInWindowsEvent, MidiInCoreMidiEvent, MidiInCoreMidiVirtualEvent, MidiInALSAEvent, Rs232TesterEvent, ImGuiDelayedActionEvent, ImGuiActiveEvent > Event
Definition: Event.hh:450
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:171
bool foreach_file(std::string path, FileAction fileAction)
int exitCode
Definition: Reactor.cc:63
const FileContext & preferSystemFileContext()
Definition: FileContext.cc:163
bool foreach_file_and_directory(std::string path, FileAction fileAction, DirAction dirAction)
auto unique(ForwardRange &&range)
Definition: ranges.hh:222
auto copy(InputRange &&range, OutputIter out)
Definition: ranges.hh:250
auto find(InputRange &&range, const T &value)
Definition: ranges.hh:160
constexpr void sort(RandomAccessRange &&range)
Definition: ranges.hh:49
size_t size(std::string_view utf8)
constexpr auto transform(Range &&range, UnaryOp op)
Definition: view.hh:520
ITER find_unguarded(ITER first, ITER last, const VAL &val, Proj proj={})
Faster alternative to 'find' when it's guaranteed that the value will be found (if not the behavior i...
Definition: stl.hh:72
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
Definition: stl.hh:134
auto rfind_unguarded(RANGE &range, const VAL &val, Proj proj={})
Similar to the find(_if)_unguarded functions above, but searches from the back to front.
Definition: stl.hh:109
constexpr bool contains(ITER first, ITER last, const VAL &val)
Check if a range contains a given value, using linear search.
Definition: stl.hh:32
std::string strCat(Ts &&...ts)
Definition: strCat.hh:625
#define UNREACHABLE
Definition: unreachable.hh:38
constexpr auto end(const zstring_view &x)