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