openMSX
RealTime.cc
Go to the documentation of this file.
1 #include "RealTime.hh"
2 #include "Timer.hh"
3 #include "EventDistributor.hh"
4 #include "EventDelay.hh"
5 #include "Event.hh"
6 #include "FinishFrameEvent.hh"
7 #include "GlobalSettings.hh"
8 #include "MSXMotherBoard.hh"
9 #include "Reactor.hh"
10 #include "IntegerSetting.hh"
11 #include "BooleanSetting.hh"
12 #include "ThrottleManager.hh"
13 #include "checked_cast.hh"
14 
15 namespace openmsx {
16 
17 const double SYNC_INTERVAL = 0.08; // s
18 const int64_t MAX_LAG = 200000; // us
19 const uint64_t ALLOWED_LAG = 20000; // us
20 
22  MSXMotherBoard& motherBoard_, GlobalSettings& globalSettings,
23  EventDelay& eventDelay_)
24  : Schedulable(motherBoard_.getScheduler())
25  , motherBoard(motherBoard_)
26  , eventDistributor(motherBoard.getReactor().getEventDistributor())
27  , eventDelay(eventDelay_)
28  , throttleManager(globalSettings.getThrottleManager())
29  , speedSetting (globalSettings.getSpeedSetting())
30  , pauseSetting (globalSettings.getPauseSetting())
31  , powerSetting (globalSettings.getPowerSetting())
32  , emuTime(EmuTime::zero())
33  , enabled(true)
34 {
35  speedSetting.attach(*this);
36  throttleManager.attach(*this);
37  pauseSetting.attach(*this);
38  powerSetting.attach(*this);
39 
40  resync();
41 
42  eventDistributor.registerEventListener(OPENMSX_FINISH_FRAME_EVENT, *this);
43  eventDistributor.registerEventListener(OPENMSX_FRAME_DRAWN_EVENT, *this);
44 }
45 
47 {
48  eventDistributor.unregisterEventListener(OPENMSX_FRAME_DRAWN_EVENT, *this);
49  eventDistributor.unregisterEventListener(OPENMSX_FINISH_FRAME_EVENT, *this);
50 
51  powerSetting.detach(*this);
52  pauseSetting.detach(*this);
53  throttleManager.detach(*this);
54  speedSetting.detach(*this);
55 }
56 
57 double RealTime::getRealDuration(EmuTime::param time1, EmuTime::param time2)
58 {
59  return (time2 - time1).toDouble() * 100.0 / speedSetting.getInt();
60 }
61 
63 {
64  return EmuDuration(realDur * speedSetting.getInt() / 100.0);
65 }
66 
67 bool RealTime::timeLeft(uint64_t us, EmuTime::param time)
68 {
69  auto realDuration = static_cast<uint64_t>(
70  getRealDuration(emuTime, time) * 1000000ULL);
71  auto currentRealTime = Timer::getTime();
72  return (currentRealTime + us) <
73  (idealRealTime + realDuration + ALLOWED_LAG);
74 }
75 
76 void RealTime::sync(EmuTime::param time, bool allowSleep)
77 {
78  if (allowSleep) {
80  }
81  internalSync(time, allowSleep);
82  if (allowSleep) {
83  setSyncPoint(time + getEmuDuration(SYNC_INTERVAL));
84  }
85 }
86 
87 void RealTime::internalSync(EmuTime::param time, bool allowSleep)
88 {
89  if (throttleManager.isThrottled()) {
90  auto realDuration = static_cast<uint64_t>(
91  getRealDuration(emuTime, time) * 1000000ULL);
92  idealRealTime += realDuration;
93  auto currentRealTime = Timer::getTime();
94  int64_t sleep = idealRealTime - currentRealTime;
95  if (allowSleep) {
96  // want to sleep for 'sleep' us
97  sleep += static_cast<int64_t>(sleepAdjust);
98  int64_t delta = 0;
99  if (sleep > 0) {
100  Timer::sleep(sleep); // request to sleep for 'sleep+sleepAdjust'
101  int64_t slept = Timer::getTime() - currentRealTime;
102  delta = sleep - slept; // actually slept for 'slept' us
103  }
104  const double ALPHA = 0.2;
105  sleepAdjust = sleepAdjust * (1 - ALPHA) + delta * ALPHA;
106  }
107  if (-sleep > MAX_LAG) {
108  idealRealTime = currentRealTime - MAX_LAG / 2;
109  }
110  }
111  if (allowSleep) {
112  eventDelay.sync(time);
113  }
114 
115  emuTime = time;
116 }
117 
118 void RealTime::executeUntil(EmuTime::param time)
119 {
120  internalSync(time, true);
121  setSyncPoint(time + getEmuDuration(SYNC_INTERVAL));
122 }
123 
124 int RealTime::signalEvent(const std::shared_ptr<const Event>& event)
125 {
126  if (!motherBoard.isActive() || !enabled) {
127  // these are global events, only the active machine should
128  // synchronize with real time
129  return 0;
130  }
131  if (event->getType() == OPENMSX_FINISH_FRAME_EVENT) {
132  auto& ffe = checked_cast<const FinishFrameEvent&>(*event);
133  if (!ffe.needRender()) {
134  // sync but don't sleep
135  sync(getCurrentTime(), false);
136  }
137  } else if (event->getType() == OPENMSX_FRAME_DRAWN_EVENT) {
138  // sync and possibly sleep
139  sync(getCurrentTime(), true);
140  }
141  return 0;
142 }
143 
144 void RealTime::update(const Setting& /*setting*/)
145 {
146  resync();
147 }
148 
149 void RealTime::update(const ThrottleManager& /*throttleManager*/)
150 {
151  resync();
152 }
153 
155 {
156  if (!enabled) return;
157 
158  idealRealTime = Timer::getTime();
159  sleepAdjust = 0.0;
160  removeSyncPoint();
161  emuTime = getCurrentTime();
162  setSyncPoint(emuTime + getEmuDuration(SYNC_INTERVAL));
163 }
164 
166 {
167  enabled = true;
168  resync();
169 }
170 
172 {
173  enabled = false;
174  removeSyncPoint();
175 }
176 
177 } // namespace openmsx
Sent when a OPENMSX_FINISH_FRAME_EVENT caused a redraw of the screen.
Definition: Event.hh:43
This class is responsible for translating host events into MSX events.
Definition: EventDelay.hh:26
void sync(EmuTime::param curEmu)
Definition: EventDelay.cc:86
void registerEventListener(EventType type, EventListener &listener, Priority priority=OTHER)
Registers a given object to receive certain events.
RealTime(MSXMotherBoard &motherBoard, GlobalSettings &globalSettings, EventDelay &eventDelay)
Definition: RealTime.cc:21
void unregisterEventListener(EventType type, EventListener &listener)
Unregisters a previously registered event listener.
EmuTime::param getCurrentTime() const
Convenience method: This is the same as getScheduler().getCurrentTime().
Definition: Schedulable.cc:49
const double SYNC_INTERVAL
Definition: RealTime.cc:17
const int64_t MAX_LAG
Definition: RealTime.cc:18
bool timeLeft(uint64_t us, EmuTime::param time)
Check that there is enough real time left before we reach as certain point in emulated time...
Definition: RealTime.cc:67
bool isThrottled() const
Ask if throttling is enabled.
void attach(Observer< T > &observer)
Definition: Subject.hh:50
Sent when VDP (V99x8 or V9990) reaches the end of a frame.
Definition: Event.hh:37
Every class that wants to get scheduled at some point must inherit from this class.
Definition: Schedulable.hh:33
const uint64_t ALLOWED_LAG
Definition: RealTime.cc:19
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
double getRealDuration(EmuTime::param time1, EmuTime::param time2)
Convert EmuTime to RealTime.
Definition: RealTime.cc:57
void sleep(uint64_t us)
Sleep for the specified amount of time (in us).
Definition: Timer.cc:28
EmuDuration getEmuDuration(double realDur)
Convert RealTime to EmuTime.
Definition: RealTime.cc:62
Manages the throttle state of openMSX.
int getInt() const noexcept
void detach(Observer< T > &observer)
Definition: Subject.hh:56
uint64_t getTime()
Get current (real) time in us.
Definition: Timer.cc:7
This class contains settings that are used by several other class (including some singletons)...
void setSyncPoint(EmuTime::param timestamp)
Definition: Schedulable.cc:23
This event is send when a device (v99x8, v9990, video9000, laserdisc) reaches the end of a frame...