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 using enum EventType;
132 for (auto type : {KEY_UP, KEY_DOWN,
136 eventDistributor.registerEventListener(type, *this);
137 }
138}
139
141{
142 for (auto idx : afterCmds) {
143 afterCmdPool.remove(idx);
144 }
145
146 using enum EventType;
147 for (auto type : {AFTER_TIMED, MACHINE_LOADED, BOOT, QUIT, BREAK, FINISH_FRAME,
150 KEY_DOWN, KEY_UP}) {
151 eventDistributor.unregisterEventListener(type, *this);
152 }
153}
154
155void AfterCommand::execute(std::span<const TclObject> tokens, TclObject& result)
156{
157 if (tokens.size() < 2) {
158 throw CommandException("Missing argument");
159 }
160 std::string_view subCmd = tokens[1].getString();
161 if (subCmd == "time") {
162 afterTime(tokens, result);
163 } else if (subCmd == "realtime") {
164 afterRealTime(tokens, result);
165 } else if (subCmd == "idle") {
166 afterIdle(tokens, result);
167 } else if (subCmd == "frame") {
168 afterSimpleEvent(tokens, result, EventType::FINISH_FRAME);
169 } else if (subCmd == "break") {
170 afterSimpleEvent(tokens, result, EventType::BREAK);
171 } else if (subCmd == "quit") {
172 afterSimpleEvent(tokens, result, EventType::QUIT);
173 } else if (subCmd == "boot") {
174 afterSimpleEvent(tokens, result, EventType::BOOT);
175 } else if (subCmd == "machine_switch") {
176 afterSimpleEvent(tokens, result, EventType::MACHINE_LOADED);
177 } else if (subCmd == "info") {
178 afterInfo(tokens, result);
179 } else if (subCmd == "cancel") {
180 afterCancel(tokens, result);
181 } else {
182 // A valid integer?
183 if (auto time = tokens[1].getOptionalInt()) {
184 afterTclTime(*time, tokens, result);
185 } else {
186 // A valid event name?
187 try {
188 afterInputEvent(
190 tokens[1], getInterpreter()),
191 tokens, result);
192 } catch (MSXException&) {
193 throw SyntaxError();
194 }
195 }
196 }
197}
198
199static double getTime(Interpreter& interp, const TclObject& obj)
200{
201 double time = obj.getDouble(interp);
202 if (time < 0) {
203 throw CommandException("Not a valid time specification");
204 }
205 return time;
206}
207
208void AfterCommand::afterTime(std::span<const TclObject> tokens, TclObject& result)
209{
210 checkNumArgs(tokens, 4, Prefix{2}, "seconds command");
211 MSXMotherBoard* motherBoard = reactor.getMotherBoard();
212 if (!motherBoard) return;
213 double time = getTime(getInterpreter(), tokens[2]);
214 auto [idx, ptr] = afterCmdPool.emplace(
215 std::in_place_type_t<AfterTimeCmd>{},
216 motherBoard->getScheduler(), *this, tokens[3], time);
217 result = std::get<AfterTimeCmd>(*ptr).getIdStr();
218 afterCmds.push_back(idx);
219}
220
221void AfterCommand::afterRealTime(std::span<const TclObject> tokens, TclObject& result)
222{
223 checkNumArgs(tokens, 4, Prefix{2}, "seconds command");
224 double time = getTime(getInterpreter(), tokens[2]);
225 auto [idx, ptr] = afterCmdPool.emplace(
226 std::in_place_type_t<AfterRealTimeCmd>{},
227 reactor.getRTScheduler(), *this, tokens[3], time);
228 result = std::get<AfterRealTimeCmd>(*ptr).getIdStr();
229 afterCmds.push_back(idx);
230}
231
232void AfterCommand::afterTclTime(
233 int ms, std::span<const TclObject> tokens, TclObject& result)
234{
235 TclObject command;
236 command.addListElements(view::drop(tokens, 2));
237 auto [idx, ptr] = afterCmdPool.emplace(
238 std::in_place_type_t<AfterRealTimeCmd>{},
239 reactor.getRTScheduler(), *this, command, ms * (1.0 / 1000.0));
240 result = std::get<AfterRealTimeCmd>(*ptr).getIdStr();
241 afterCmds.push_back(idx);
242}
243
244void AfterCommand::afterSimpleEvent(std::span<const TclObject> tokens, TclObject& result, EventType type)
245{
246 checkNumArgs(tokens, 3, "command");
247 auto [idx, ptr] = afterCmdPool.emplace(
248 std::in_place_type_t<AfterSimpleEventCmd>{},
249 *this, tokens[2], type);
250 result = std::get<AfterSimpleEventCmd>(*ptr).getIdStr();
251 afterCmds.push_back(idx);
252}
253
254void AfterCommand::afterInputEvent(
255 Event event, std::span<const TclObject> tokens, TclObject& result)
256{
257 checkNumArgs(tokens, 3, "command");
258 auto [idx, ptr] = afterCmdPool.emplace(
259 std::in_place_type_t<AfterInputEventCmd>{},
260 *this, std::move(event), tokens[2]);
261 result = std::get<AfterInputEventCmd>(*ptr).getIdStr();
262 afterCmds.push_back(idx);
263}
264
265void AfterCommand::afterIdle(std::span<const TclObject> tokens, TclObject& result)
266{
267 checkNumArgs(tokens, 4, Prefix{2}, "seconds command");
268 MSXMotherBoard* motherBoard = reactor.getMotherBoard();
269 if (!motherBoard) return;
270 double time = getTime(getInterpreter(), tokens[2]);
271 auto [idx, ptr] = afterCmdPool.emplace(
272 std::in_place_type_t<AfterIdleCmd>{},
273 motherBoard->getScheduler(), *this, tokens[3], time);
274 result = std::get<AfterIdleCmd>(*ptr).getIdStr();
275 afterCmds.push_back(idx);
276}
277
278void AfterCommand::afterInfo(std::span<const TclObject> /*tokens*/, TclObject& result) const
279{
280 auto printTime = [](std::ostream& os, const AfterTimedCmd& cmd) {
281 os.precision(3);
282 os << std::fixed << std::showpoint << cmd.getTime() << ' ';
283 };
284
285 std::ostringstream str;
286 for (auto idx : afterCmds) {
287 const auto& var = afterCmdPool[idx];
288 std::visit([&](const AfterCmd& cmd) { str << cmd.getIdStr() << ": "; }, var);
289 std::visit(overloaded {
290 [&](const AfterTimeCmd& cmd ) { str << "time "; printTime(str, cmd); },
291 [&](const AfterIdleCmd& cmd ) { str << "idle "; printTime(str, cmd); },
292 [&](const AfterSimpleEventCmd& cmd ) { str << cmd.getType() << ' '; },
293 [&](const AfterInputEventCmd& cmd ) { str << toString(cmd.getEvent()) << ' '; },
294 [&](const AfterRealTimeCmd& /*cmd*/) { str << "realtime "; }
295 }, var);
296 std::visit([&](const AfterCmd& cmd) { str << cmd.getCommand().getString() << '\n'; }, var);
297 }
298 result = str.str();
299}
300
301void AfterCommand::afterCancel(std::span<const TclObject> tokens, TclObject& /*result*/)
302{
303 checkNumArgs(tokens, AtLeast{3}, "id|command");
304 if (tokens.size() == 3) {
305 if (auto idStr = tokens[2].getString(); idStr.starts_with("after#")) {
306 if (auto id = StringOp::stringTo<unsigned>(idStr.substr(6))) {
307 auto equalId = [id = *id](Index idx) {
308 return std::visit([&](const AfterCmd& cmd) {
309 return cmd.getId() == id;
310 }, afterCmdPool[idx]);
311 };
312 if (auto it = ranges::find_if(afterCmds, equalId);
313 it != end(afterCmds)) {
314 auto idx = *it;
315 afterCmds.erase(it);
316 afterCmdPool.remove(idx);
317 return;
318 }
319 }
320 }
321 }
322 TclObject command;
323 command.addListElements(view::drop(tokens, 2));
324 std::string_view cmdStr = command.getString();
325 auto equalCmd = [&](Index idx) {
326 return std::visit([&](const AfterCmd& cmd) {
327 return cmd.getCommand() == cmdStr;
328 }, afterCmdPool[idx]);
329 };
330 if (auto it = ranges::find_if(afterCmds, equalCmd);
331 it != end(afterCmds)) {
332 auto idx = *it;
333 afterCmds.erase(it);
334 afterCmdPool.remove(idx);
335 // Tcl manual is not clear about this, but it seems
336 // there's only occurrences of this command canceled.
337 // It's also not clear which of the (possibly) several
338 // matches is canceled.
339 return;
340 }
341 // It's not an error if no match is found
342}
343
344std::string AfterCommand::help(std::span<const TclObject> /*tokens*/) const
345{
346 return "after time <seconds> <command> execute a command after some time (MSX time)\n"
347 "after realtime <seconds> <command> execute a command after some time (realtime)\n"
348 "after idle <seconds> <command> execute a command after some time being idle\n"
349 "after frame <command> execute a command after a new frame is drawn\n"
350 "after break <command> execute a command after a breakpoint is reached\n"
351 "after boot <command> execute a command after a (re)boot\n"
352 "after machine_switch <command> execute a command after a switch to a new machine\n"
353 "after info list all postponed commands\n"
354 "after cancel <id> cancel the postponed command with given id\n";
355}
356
357void AfterCommand::tabCompletion(std::vector<std::string>& tokens) const
358{
359 if (tokens.size() == 2) {
360 using namespace std::literals;
361 static constexpr std::array cmds = {
362 "time"sv, "realtime"sv, "idle"sv, "frame"sv, "break"sv, "boot"sv,
363 "machine_switch"sv, "info"sv, "cancel"sv,
364 };
365 completeString(tokens, cmds);
366 }
367 // TODO : make more complete
368}
369
370// Execute the cmds for which the predicate returns true, and erase those from afterCmds.
371void AfterCommand::executeMatches(std::predicate<Index> auto pred)
372{
373 static std::vector<Index> matches; // static to keep capacity for next call
374 assert(matches.empty());
375
376 // Usually there are very few matches (typically even 0 or 1), so no
377 // need to reserve() space.
378 auto p = partition_copy_remove(afterCmds, std::back_inserter(matches), pred);
379 afterCmds.erase(p.second, end(afterCmds));
380 for (auto idx : matches) {
381 std::visit([](AfterCmd& cmd) { cmd.execute(); },
382 afterCmdPool[idx]);
383 afterCmdPool.remove(idx);
384 }
385 matches.clear(); // for next call (but keep capacity)
386}
387
390 return std::visit(overloaded {
391 [&](const AfterSimpleEventCmd& cmd) { return cmd.getTypeEnum() == type; },
392 [&](const AfterCmd& /*cmd*/) { return false; }
393 }, afterCmdPool[idx]);
394 }
396};
397void AfterCommand::executeSimpleEvents(EventType type)
398{
399 executeMatches(AfterSimpleEventPred{type});
400}
401
404 return std::visit(overloaded {
405 [&](const AfterTimedCmd& cmd) { return cmd.getTime() == 0.0; },
406 [&](const AfterCmd& /*cmd*/) { return false; }
407 }, afterCmdPool[idx]);
408 }
409};
410
412 explicit AfterInputEventPred(const Event& event_)
413 : event(event_) {}
415 return std::visit(overloaded {
416 [&](const AfterInputEventCmd& cmd) { return matches(cmd.getEvent(), event); },
417 [&](const AfterCmd& /*cmd*/) { return false; }
418 }, afterCmdPool[idx]);
419 }
420 const Event& event;
421};
422
423int AfterCommand::signalEvent(const Event& event)
424{
425 std::visit(overloaded{
426 [&](const SimpleEvent&) {
427 executeSimpleEvents(getType(event));
428 },
429 [&](const FinishFrameEvent&) {
430 executeSimpleEvents(EventType::FINISH_FRAME);
431 },
432 [&](const QuitEvent&) {
433 executeSimpleEvents(EventType::QUIT);
434 },
435 [&](const AfterTimedEvent&) {
436 executeMatches(AfterEmuTimePred());
437 },
438 [&](const EventBase&) {
439 executeMatches(AfterInputEventPred(event));
440 for (auto idx : afterCmds) {
441 std::visit(overloaded {
442 [](AfterIdleCmd& cmd) { cmd.reschedule(); },
443 [](const AfterCmd& /*cmd*/) { /*nothing*/ }
444 }, afterCmdPool[idx]);
445 }
446 }
447 }, event);
448 return 0;
449}
450
451
452// class AfterCmd
453
455 : afterCommand(afterCommand_), command(std::move(command_)), id(++lastAfterId)
456{
457}
458
460{
461 try {
463 } catch (CommandException& e) {
465 "Error executing delayed command: ", e.getMessage());
466 }
467}
468
470{
471 auto equalThis = [&](AfterCommand::Index idx) {
472 return std::visit([&](const AfterCmd& cmd) { return &cmd == this; },
473 afterCmdPool[idx]);
474 };
475 auto it = rfind_if_unguarded(afterCommand.afterCmds, equalThis);
476 auto idx = *it;
477 afterCommand.afterCmds.erase(it); // move_pop_back ?
478 return idx;
479}
480
481
482// class AfterTimedCmd
483
485 Scheduler& scheduler_,
486 AfterCommand& afterCommand_,
487 TclObject command_, double time_)
488 : AfterCmd(afterCommand_, std::move(command_))
489 , Schedulable(scheduler_)
490 , time(time_)
491{
492 reschedule();
493}
494
496{
497 return time;
498}
499
505
506void AfterTimedCmd::executeUntil(EmuTime::param /*time*/)
507{
508 time = 0.0; // execute on next event
509 afterCommand.eventDistributor.distributeEvent(AfterTimedEvent());
510}
511
512void AfterTimedCmd::schedulerDeleted()
513{
514 auto idx = removeSelf();
515 afterCmdPool.remove(idx);
516}
517
518
519// class AfterTimeCmd
520
522 Scheduler& scheduler_,
523 AfterCommand& afterCommand_,
524 TclObject command_, double time_)
525 : AfterTimedCmd(scheduler_, afterCommand_, std::move(command_), time_)
526{
527}
528
529
530// class AfterIdleCmd
531
533 Scheduler& scheduler_,
534 AfterCommand& afterCommand_,
535 TclObject command_, double time_)
536 : AfterTimedCmd(scheduler_, afterCommand_, std::move(command_), time_)
537{
538}
539
540
541// class AfterSimpleEventCmd
542
544 AfterCommand& afterCommand_, TclObject command_,
545 EventType type_)
546 : AfterCmd(afterCommand_, std::move(command_)), type(type_)
547{
548}
549
550std::string_view AfterSimpleEventCmd::getType() const
551{
552 switch (type) {
553 using enum EventType;
554 case FINISH_FRAME: return "frame";
555 case BREAK: return "break";
556 case BOOT: return "boot";
557 case QUIT: return "quit";
558 case MACHINE_LOADED: return "machine_switch";
559 default: UNREACHABLE;
560 }
561}
562
563
564// AfterInputEventCmd
565
567 AfterCommand& afterCommand_,
568 Event event_,
569 TclObject command_)
570 : AfterCmd(afterCommand_, std::move(command_))
571 , event(std::move(event_))
572{
573}
574
575
576// class AfterRealTimeCmd
577
579 RTScheduler& rtScheduler, AfterCommand& afterCommand_,
580 TclObject command_, double time)
581 : AfterCmd(afterCommand_, std::move(command_))
582 , RTSchedulable(rtScheduler)
583{
584 scheduleRT(uint64_t(time * 1e6)); // micro seconds
585}
586
587void AfterRealTimeCmd::executeRT()
588{
589 // Remove self before executing, but keep self alive till the end of
590 // this method. Otherwise execute could execute 'after cancel ..' and
591 // removeSelf() asserts that it can't find itself anymore.
592 auto idx = removeSelf();
593 execute();
594 afterCmdPool.remove(idx);
595}
596
597} // 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:386
void printWarning(std::string_view message)
Definition CliComm.cc:10
CommandController & getCommandController() const
Definition Command.hh:28
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:138
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:315
void scheduleRT(uint64_t delta)
Contains the main loop of openMSX.
Definition Reactor.hh:74
MSXMotherBoard * getMotherBoard() const
Definition Reactor.cc:409
RTScheduler & getRTScheduler()
Definition Reactor.hh:87
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:455
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:518
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:446
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)