openMSX
AfterCommand.cc
Go to the documentation of this file.
1#include "AfterCommand.hh"
2
3#include "CliComm.hh"
5#include "CommandException.hh"
6#include "EmuTime.hh"
7#include "Event.hh"
8#include "EventDistributor.hh"
10#include "MSXMotherBoard.hh"
11#include "ObjectPool.hh"
12#include "RTSchedulable.hh"
13#include "Reactor.hh"
14#include "Schedulable.hh"
15#include "TclObject.hh"
16
17#include "StringOp.hh"
18#include "ranges.hh"
19#include "stl.hh"
20#include "unreachable.hh"
21#include "view.hh"
22
23#include <iterator>
24#include <memory>
25#include <sstream>
26#include <variant>
27
28namespace openmsx {
29
31{
32public:
33 [[nodiscard]] const auto& getCommand() const { return command; }
34 [[nodiscard]] auto getId() const { return id; }
35 [[nodiscard]] auto getIdStr() const { return tmpStrCat("after#", id); }
36 void execute();
37protected:
41
44 const unsigned id;
45 static inline unsigned lastAfterId = 0;
46};
47
48class AfterTimedCmd : public AfterCmd, private Schedulable
49{
50public:
51 [[nodiscard]] double getTime() const;
52 void reschedule();
53protected:
54 AfterTimedCmd(Scheduler& scheduler,
56 TclObject command, double time);
57private:
58 void executeUntil(EmuTime::param time) override;
59 void schedulerDeleted() override;
60
61 double time; // Zero when expired, otherwise the original duration (to
62 // be able to reschedule for 'after idle').
63};
64
65class AfterTimeCmd final : public AfterTimedCmd
66{
67public:
68 AfterTimeCmd(Scheduler& scheduler,
70 TclObject command, double time);
71};
72
73class AfterIdleCmd final : public AfterTimedCmd
74{
75public:
76 AfterIdleCmd(Scheduler& scheduler,
78 TclObject command, double time);
79};
80
81class AfterSimpleEventCmd final : public AfterCmd
82{
83public:
86 EventType type);
87 [[nodiscard]] std::string_view getType() const;
88 [[nodiscard]] EventType getTypeEnum() const { return type; }
89private:
90 EventType type;
91};
92
93class AfterInputEventCmd final : public AfterCmd
94{
95public:
97 Event event,
99 [[nodiscard]] const Event& getEvent() const { return event; }
100private:
101 Event event;
102};
103
104class AfterRealTimeCmd final : public AfterCmd, private RTSchedulable
105{
106public:
108 TclObject command, double time);
109
110private:
111 void executeRT() override;
112};
113
114using AllAfterCmds = std::variant<AfterTimeCmd,
119static ObjectPool<AllAfterCmds> afterCmdPool;
120
121
123 EventDistributor& eventDistributor_,
124 CommandController& commandController_)
125 : Command(commandController_, "after")
126 , reactor(reactor_)
127 , eventDistributor(eventDistributor_)
128{
129 // TODO DETACHED <-> EMU types should be cleaned up
130 // (moved to event iso listener?)
131 for (auto type : {EventType::KEY_UP,
147 eventDistributor.registerEventListener(type, *this);
148 }
149}
150
176
177void AfterCommand::execute(std::span<const TclObject> tokens, TclObject& result)
178{
179 if (tokens.size() < 2) {
180 throw CommandException("Missing argument");
181 }
182 std::string_view subCmd = tokens[1].getString();
183 if (subCmd == "time") {
184 afterTime(tokens, result);
185 } else if (subCmd == "realtime") {
186 afterRealTime(tokens, result);
187 } else if (subCmd == "idle") {
188 afterIdle(tokens, result);
189 } else if (subCmd == "frame") {
190 afterSimpleEvent(tokens, result, EventType::FINISH_FRAME);
191 } else if (subCmd == "break") {
192 afterSimpleEvent(tokens, result, EventType::BREAK);
193 } else if (subCmd == "quit") {
194 afterSimpleEvent(tokens, result, EventType::QUIT);
195 } else if (subCmd == "boot") {
196 afterSimpleEvent(tokens, result, EventType::BOOT);
197 } else if (subCmd == "machine_switch") {
198 afterSimpleEvent(tokens, result, EventType::MACHINE_LOADED);
199 } else if (subCmd == "info") {
200 afterInfo(tokens, result);
201 } else if (subCmd == "cancel") {
202 afterCancel(tokens, result);
203 } else {
204 // A valid integer?
205 if (auto time = tokens[1].getOptionalInt()) {
206 afterTclTime(*time, tokens, result);
207 } else {
208 // A valid event name?
209 try {
210 afterInputEvent(
212 tokens[1], getInterpreter()),
213 tokens, result);
214 } catch (MSXException&) {
215 throw SyntaxError();
216 }
217 }
218 }
219}
220
221static double getTime(Interpreter& interp, const TclObject& obj)
222{
223 double time = obj.getDouble(interp);
224 if (time < 0) {
225 throw CommandException("Not a valid time specification");
226 }
227 return time;
228}
229
230void AfterCommand::afterTime(std::span<const TclObject> tokens, TclObject& result)
231{
232 checkNumArgs(tokens, 4, Prefix{2}, "seconds command");
233 MSXMotherBoard* motherBoard = reactor.getMotherBoard();
234 if (!motherBoard) return;
235 double time = getTime(getInterpreter(), tokens[2]);
236 auto [idx, ptr] = afterCmdPool.emplace(
237 std::in_place_type_t<AfterTimeCmd>{},
238 motherBoard->getScheduler(), *this, tokens[3], time);
239 result = std::get<AfterTimeCmd>(*ptr).getIdStr();
240 afterCmds.push_back(idx);
241}
242
243void AfterCommand::afterRealTime(std::span<const TclObject> tokens, TclObject& result)
244{
245 checkNumArgs(tokens, 4, Prefix{2}, "seconds command");
246 double time = getTime(getInterpreter(), tokens[2]);
247 auto [idx, ptr] = afterCmdPool.emplace(
248 std::in_place_type_t<AfterRealTimeCmd>{},
249 reactor.getRTScheduler(), *this, tokens[3], time);
250 result = std::get<AfterRealTimeCmd>(*ptr).getIdStr();
251 afterCmds.push_back(idx);
252}
253
254void AfterCommand::afterTclTime(
255 int ms, std::span<const TclObject> tokens, TclObject& result)
256{
257 TclObject command;
258 command.addListElements(view::drop(tokens, 2));
259 auto [idx, ptr] = afterCmdPool.emplace(
260 std::in_place_type_t<AfterRealTimeCmd>{},
261 reactor.getRTScheduler(), *this, command, ms * (1.0 / 1000.0));
262 result = std::get<AfterRealTimeCmd>(*ptr).getIdStr();
263 afterCmds.push_back(idx);
264}
265
266void AfterCommand::afterSimpleEvent(std::span<const TclObject> tokens, TclObject& result, EventType type)
267{
268 checkNumArgs(tokens, 3, "command");
269 auto [idx, ptr] = afterCmdPool.emplace(
270 std::in_place_type_t<AfterSimpleEventCmd>{},
271 *this, tokens[2], type);
272 result = std::get<AfterSimpleEventCmd>(*ptr).getIdStr();
273 afterCmds.push_back(idx);
274}
275
276void AfterCommand::afterInputEvent(
277 Event event, std::span<const TclObject> tokens, TclObject& result)
278{
279 checkNumArgs(tokens, 3, "command");
280 auto [idx, ptr] = afterCmdPool.emplace(
281 std::in_place_type_t<AfterInputEventCmd>{},
282 *this, std::move(event), tokens[2]);
283 result = std::get<AfterInputEventCmd>(*ptr).getIdStr();
284 afterCmds.push_back(idx);
285}
286
287void AfterCommand::afterIdle(std::span<const TclObject> tokens, TclObject& result)
288{
289 checkNumArgs(tokens, 4, Prefix{2}, "seconds command");
290 MSXMotherBoard* motherBoard = reactor.getMotherBoard();
291 if (!motherBoard) return;
292 double time = getTime(getInterpreter(), tokens[2]);
293 auto [idx, ptr] = afterCmdPool.emplace(
294 std::in_place_type_t<AfterIdleCmd>{},
295 motherBoard->getScheduler(), *this, tokens[3], time);
296 result = std::get<AfterIdleCmd>(*ptr).getIdStr();
297 afterCmds.push_back(idx);
298}
299
300void AfterCommand::afterInfo(std::span<const TclObject> /*tokens*/, TclObject& result) const
301{
302 auto printTime = [](std::ostream& os, const AfterTimedCmd& cmd) {
303 os.precision(3);
304 os << std::fixed << std::showpoint << cmd.getTime() << ' ';
305 };
306
307 std::ostringstream str;
308 for (auto idx : afterCmds) {
309 const auto& var = afterCmdPool[idx];
310 std::visit([&](const AfterCmd& cmd) { str << cmd.getIdStr() << ": "; }, var);
311 std::visit(overloaded {
312 [&](const AfterTimeCmd& cmd ) { str << "time "; printTime(str, cmd); },
313 [&](const AfterIdleCmd& cmd ) { str << "idle "; printTime(str, cmd); },
314 [&](const AfterSimpleEventCmd& cmd ) { str << cmd.getType() << ' '; },
315 [&](const AfterInputEventCmd& cmd ) { str << toString(cmd.getEvent()) << ' '; },
316 [&](const AfterRealTimeCmd& /*cmd*/) { str << "realtime "; }
317 }, var);
318 std::visit([&](const AfterCmd& cmd) { str << cmd.getCommand().getString() << '\n'; }, var);
319 }
320 result = str.str();
321}
322
323void AfterCommand::afterCancel(std::span<const TclObject> tokens, TclObject& /*result*/)
324{
325 checkNumArgs(tokens, AtLeast{3}, "id|command");
326 if (tokens.size() == 3) {
327 if (auto idStr = tokens[2].getString(); idStr.starts_with("after#")) {
328 if (auto id = StringOp::stringTo<unsigned>(idStr.substr(6))) {
329 auto equalId = [id = *id](Index idx) {
330 return std::visit([&](const AfterCmd& cmd) {
331 return cmd.getId() == id;
332 }, afterCmdPool[idx]);
333 };
334 if (auto it = ranges::find_if(afterCmds, equalId);
335 it != end(afterCmds)) {
336 auto idx = *it;
337 afterCmds.erase(it);
338 afterCmdPool.remove(idx);
339 return;
340 }
341 }
342 }
343 }
344 TclObject command;
345 command.addListElements(view::drop(tokens, 2));
346 std::string_view cmdStr = command.getString();
347 auto equalCmd = [&](Index idx) {
348 return std::visit([&](const AfterCmd& cmd) {
349 return cmd.getCommand() == cmdStr;
350 }, afterCmdPool[idx]);
351 };
352 if (auto it = ranges::find_if(afterCmds, equalCmd);
353 it != end(afterCmds)) {
354 auto idx = *it;
355 afterCmds.erase(it);
356 afterCmdPool.remove(idx);
357 // Tcl manual is not clear about this, but it seems
358 // there's only occurrences of this command canceled.
359 // It's also not clear which of the (possibly) several
360 // matches is canceled.
361 return;
362 }
363 // It's not an error if no match is found
364}
365
366std::string AfterCommand::help(std::span<const TclObject> /*tokens*/) const
367{
368 return "after time <seconds> <command> execute a command after some time (MSX time)\n"
369 "after realtime <seconds> <command> execute a command after some time (realtime)\n"
370 "after idle <seconds> <command> execute a command after some time being idle\n"
371 "after frame <command> execute a command after a new frame is drawn\n"
372 "after break <command> execute a command after a breakpoint is reached\n"
373 "after boot <command> execute a command after a (re)boot\n"
374 "after machine_switch <command> execute a command after a switch to a new machine\n"
375 "after info list all postponed commands\n"
376 "after cancel <id> cancel the postponed command with given id\n";
377}
378
379void AfterCommand::tabCompletion(std::vector<std::string>& tokens) const
380{
381 if (tokens.size() == 2) {
382 using namespace std::literals;
383 static constexpr std::array cmds = {
384 "time"sv, "realtime"sv, "idle"sv, "frame"sv, "break"sv, "boot"sv,
385 "machine_switch"sv, "info"sv, "cancel"sv,
386 };
387 completeString(tokens, cmds);
388 }
389 // TODO : make more complete
390}
391
392// Execute the cmds for which the predicate returns true, and erase those from afterCmds.
393void AfterCommand::executeMatches(std::predicate<Index> auto pred)
394{
395 static std::vector<Index> matches; // static to keep capacity for next call
396 assert(matches.empty());
397
398 // Usually there are very few matches (typically even 0 or 1), so no
399 // need to reserve() space.
400 auto p = partition_copy_remove(afterCmds, std::back_inserter(matches), pred);
401 afterCmds.erase(p.second, end(afterCmds));
402 for (auto idx : matches) {
403 std::visit([](AfterCmd& cmd) { cmd.execute(); },
404 afterCmdPool[idx]);
405 afterCmdPool.remove(idx);
406 }
407 matches.clear(); // for next call (but keep capacity)
408}
409
412 return std::visit(overloaded {
413 [&](const AfterSimpleEventCmd& cmd) { return cmd.getTypeEnum() == type; },
414 [&](const AfterCmd& /*cmd*/) { return false; }
415 }, afterCmdPool[idx]);
416 }
418};
419void AfterCommand::executeSimpleEvents(EventType type)
420{
421 executeMatches(AfterSimpleEventPred{type});
422}
423
426 return std::visit(overloaded {
427 [&](const AfterTimedCmd& cmd) { return cmd.getTime() == 0.0; },
428 [&](const AfterCmd& /*cmd*/) { return false; }
429 }, afterCmdPool[idx]);
430 }
431};
432
434 explicit AfterInputEventPred(const Event& event_)
435 : event(event_) {}
437 return std::visit(overloaded {
438 [&](const AfterInputEventCmd& cmd) { return matches(cmd.getEvent(), event); },
439 [&](const AfterCmd& /*cmd*/) { return false; }
440 }, afterCmdPool[idx]);
441 }
442 const Event& event;
443};
444
445int AfterCommand::signalEvent(const Event& event)
446{
447 std::visit(overloaded{
448 [&](const SimpleEvent&) {
449 executeSimpleEvents(getType(event));
450 },
451 [&](const FinishFrameEvent&) {
452 executeSimpleEvents(EventType::FINISH_FRAME);
453 },
454 [&](const QuitEvent&) {
455 executeSimpleEvents(EventType::QUIT);
456 },
457 [&](const AfterTimedEvent&) {
458 executeMatches(AfterEmuTimePred());
459 },
460 [&](const EventBase&) {
461 executeMatches(AfterInputEventPred(event));
462 for (auto idx : afterCmds) {
463 std::visit(overloaded {
464 [](AfterIdleCmd& cmd) { cmd.reschedule(); },
465 [](const AfterCmd& /*cmd*/) { /*nothing*/ }
466 }, afterCmdPool[idx]);
467 }
468 }
469 }, event);
470 return 0;
471}
472
473
474// class AfterCmd
475
477 : afterCommand(afterCommand_), command(std::move(command_)), id(++lastAfterId)
478{
479}
480
482{
483 try {
485 } catch (CommandException& e) {
487 "Error executing delayed command: ", e.getMessage());
488 }
489}
490
492{
493 auto equalThis = [&](AfterCommand::Index idx) {
494 return std::visit([&](const AfterCmd& cmd) { return &cmd == this; },
495 afterCmdPool[idx]);
496 };
497 auto it = rfind_if_unguarded(afterCommand.afterCmds, equalThis);
498 auto idx = *it;
499 afterCommand.afterCmds.erase(it); // move_pop_back ?
500 return idx;
501}
502
503
504// class AfterTimedCmd
505
507 Scheduler& scheduler_,
508 AfterCommand& afterCommand_,
509 TclObject command_, double time_)
510 : AfterCmd(afterCommand_, std::move(command_))
511 , Schedulable(scheduler_)
512 , time(time_)
513{
514 reschedule();
515}
516
518{
519 return time;
520}
521
527
528void AfterTimedCmd::executeUntil(EmuTime::param /*time*/)
529{
530 time = 0.0; // execute on next event
531 afterCommand.eventDistributor.distributeEvent(AfterTimedEvent());
532}
533
534void AfterTimedCmd::schedulerDeleted()
535{
536 auto idx = removeSelf();
537 afterCmdPool.remove(idx);
538}
539
540
541// class AfterTimeCmd
542
544 Scheduler& scheduler_,
545 AfterCommand& afterCommand_,
546 TclObject command_, double time_)
547 : AfterTimedCmd(scheduler_, afterCommand_, std::move(command_), time_)
548{
549}
550
551
552// class AfterIdleCmd
553
555 Scheduler& scheduler_,
556 AfterCommand& afterCommand_,
557 TclObject command_, double time_)
558 : AfterTimedCmd(scheduler_, afterCommand_, std::move(command_), time_)
559{
560}
561
562
563// class AfterSimpleEventCmd
564
566 AfterCommand& afterCommand_, TclObject command_,
567 EventType type_)
568 : AfterCmd(afterCommand_, std::move(command_)), type(type_)
569{
570}
571
572std::string_view AfterSimpleEventCmd::getType() const
573{
574 switch (type) {
575 case EventType::FINISH_FRAME: return "frame";
576 case EventType::BREAK: return "break";
577 case EventType::BOOT: return "boot";
578 case EventType::QUIT: return "quit";
579 case EventType::MACHINE_LOADED: return "machine_switch";
580 default: UNREACHABLE;
581 }
582}
583
584
585// AfterInputEventCmd
586
588 AfterCommand& afterCommand_,
589 Event event_,
590 TclObject command_)
591 : AfterCmd(afterCommand_, std::move(command_))
592 , event(std::move(event_))
593{
594}
595
596
597// class AfterRealTimeCmd
598
600 RTScheduler& rtScheduler, AfterCommand& afterCommand_,
601 TclObject command_, double time)
602 : AfterCmd(afterCommand_, std::move(command_))
603 , RTSchedulable(rtScheduler)
604{
605 scheduleRT(uint64_t(time * 1e6)); // micro seconds
606}
607
608void AfterRealTimeCmd::executeRT()
609{
610 // Remove self before executing, but keep self alive till the end of
611 // this method. Otherwise execute could execute 'after cancel ..' and
612 // removeSelf() asserts that it can't find itself anymore.
613 auto idx = removeSelf();
614 execute();
615 afterCmdPool.remove(idx);
616}
617
618} // namespace openmsx
uintptr_t id
AfterCmd(AfterCommand &afterCommand, TclObject command)
const unsigned id
const auto & getCommand() const
auto getIdStr() const
AfterCommand & afterCommand
AfterCommand::Index removeSelf()
auto getId() const
static unsigned lastAfterId
friend class AfterTimedCmd
AfterCommand(Reactor &reactor, EventDistributor &eventDistributor, CommandController &commandController)
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
std::string help(std::span< const TclObject > tokens) const override
Print help for this command.
friend class AfterRealTimeCmd
void tabCompletion(std::vector< std::string > &tokens) const override
Attempt tab completion for this command.
AfterIdleCmd(Scheduler &scheduler, AfterCommand &afterCommand, TclObject command, double time)
AfterInputEventCmd(AfterCommand &afterCommand, Event event, TclObject command)
const Event & getEvent() const
AfterRealTimeCmd(RTScheduler &rtScheduler, AfterCommand &afterCommand, TclObject command, double time)
EventType getTypeEnum() const
std::string_view getType() const
AfterSimpleEventCmd(AfterCommand &afterCommand, TclObject command, EventType type)
AfterTimeCmd(Scheduler &scheduler, AfterCommand &afterCommand, TclObject command, double time)
AfterTimedCmd(Scheduler &scheduler, AfterCommand &afterCommand, TclObject command, double time)
Send when an after-EmuTime command should be executed.
Definition Event.hh:384
void printWarning(std::string_view message)
Definition CliComm.cc:10
CommandController & getCommandController() const
Definition Command.hh:26
Interpreter & getInterpreter() const final
Definition Command.cc:38
virtual CliComm & getCliComm()=0
static void completeString(std::vector< std::string > &tokens, ITER begin, ITER end, bool caseSensitive=true)
Definition Completer.hh:136
void checkNumArgs(std::span< const TclObject > tokens, unsigned exactly, const char *errMessage) const
Definition Completer.cc:181
void unregisterEventListener(EventType type, EventListener &listener)
Unregisters a previously registered event listener.
void distributeEvent(Event &&event)
Schedule the given event for delivery.
void registerEventListener(EventType type, EventListener &listener, Priority priority=OTHER)
Registers a given object to receive certain events.
This event is send when a device (v99x8, v9990, video9000, laserdisc) reaches the end of a frame.
Definition Event.hh:314
void scheduleRT(uint64_t delta)
Contains the main loop of openMSX.
Definition Reactor.hh:72
MSXMotherBoard * getMotherBoard() const
Definition Reactor.cc:410
RTScheduler & getRTScheduler()
Definition Reactor.hh:85
Every class that wants to get scheduled at some point must inherit from this class.
void setSyncPoint(EmuTime::param timestamp)
EmuTime::param getCurrentTime() const
Convenience method: This is the same as getScheduler().getCurrentTime().
TclObject executeCommand(Interpreter &interp, bool compile=false)
Interpret this TclObject as a command and execute it.
Definition TclObject.cc:248
double getDouble(Interpreter &interp) const
Definition TclObject.cc:122
Event createInputEvent(const TclObject &str, Interpreter &interp)
This file implemented 3 utility functions:
Definition Autofire.cc:11
EventType
Definition Event.hh:453
bool matches(const Event &self, const Event &other)
Does this event 'match' the given event.
Definition Event.cc:210
std::variant< AfterTimeCmd, AfterIdleCmd, AfterSimpleEventCmd, AfterInputEventCmd, AfterRealTimeCmd > AllAfterCmds
EventType getType(const Event &event)
Definition Event.hh:516
std::string toString(const BooleanInput &input)
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, Rs232NetEvent, ImGuiDelayedActionEvent, ImGuiActiveEvent > Event
Definition Event.hh:444
auto find_if(InputRange &&range, UnaryPredicate pred)
Definition ranges.hh:173
STL namespace.
constexpr auto drop(Range &&range, size_t n)
Definition view.hh:502
std::pair< OutputIt, ForwardIt > partition_copy_remove(ForwardIt first, ForwardIt last, OutputIt out_true, UnaryPredicate p)
This is like a combination of partition_copy() and remove().
Definition stl.hh:169
constexpr auto rfind_if_unguarded(RANGE &range, PRED pred)
Definition stl.hh:117
TemporaryString tmpStrCat(Ts &&... ts)
Definition strCat.hh:742
bool operator()(AfterCommand::Index idx) const
AfterInputEventPred(const Event &event_)
bool operator()(AfterCommand::Index idx) const
bool operator()(AfterCommand::Index idx) const
#define UNREACHABLE
constexpr auto end(const zstring_view &x)