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 #include <cassert>
15 
16 namespace openmsx {
17 
18 const double SYNC_INTERVAL = 0.08; // s
19 const int64_t MAX_LAG = 200000; // us
20 const uint64_t ALLOWED_LAG = 20000; // us
21 
23  MSXMotherBoard& motherBoard_, GlobalSettings& globalSettings,
24  EventDelay& eventDelay_)
25  : Schedulable(motherBoard_.getScheduler())
26  , motherBoard(motherBoard_)
27  , eventDistributor(motherBoard.getReactor().getEventDistributor())
28  , eventDelay(eventDelay_)
29  , throttleManager(globalSettings.getThrottleManager())
30  , speedSetting (globalSettings.getSpeedSetting())
31  , pauseSetting (globalSettings.getPauseSetting())
32  , powerSetting (globalSettings.getPowerSetting())
33  , emuTime(EmuTime::zero)
34  , enabled(true)
35 {
36  speedSetting.attach(*this);
37  throttleManager.attach(*this);
38  pauseSetting.attach(*this);
39  powerSetting.attach(*this);
40 
41  resync();
42 
43  eventDistributor.registerEventListener(OPENMSX_FINISH_FRAME_EVENT, *this);
44  eventDistributor.registerEventListener(OPENMSX_FRAME_DRAWN_EVENT, *this);
45 }
46 
48 {
49  eventDistributor.unregisterEventListener(OPENMSX_FRAME_DRAWN_EVENT, *this);
50  eventDistributor.unregisterEventListener(OPENMSX_FINISH_FRAME_EVENT, *this);
51 
52  powerSetting.detach(*this);
53  pauseSetting.detach(*this);
54  throttleManager.detach(*this);
55  speedSetting.detach(*this);
56 }
57 
59 {
60  return (time2 - time1).toDouble() * 100.0 / speedSetting.getValue();
61 }
62 
64 {
65  return EmuDuration(realDur * speedSetting.getValue() / 100.0);
66 }
67 
68 bool RealTime::timeLeft(uint64_t us, EmuTime::param time)
69 {
70  auto realDuration = static_cast<uint64_t>(
71  getRealDuration(emuTime, time) * 1000000ULL);
72  auto currentRealTime = Timer::getTime();
73  return (currentRealTime + us) <
74  (idealRealTime + realDuration + ALLOWED_LAG);
75 }
76 
77 void RealTime::sync(EmuTime::param time, bool allowSleep)
78 {
79  if (allowSleep) {
81  }
82  internalSync(time, allowSleep);
83  if (allowSleep) {
85  }
86 }
87 
88 void RealTime::internalSync(EmuTime::param time, bool allowSleep)
89 {
90  if (throttleManager.isThrottled()) {
91  auto realDuration = static_cast<uint64_t>(
92  getRealDuration(emuTime, time) * 1000000ULL);
93  idealRealTime += realDuration;
94  auto currentRealTime = Timer::getTime();
95  int64_t sleep = idealRealTime - currentRealTime;
96  if (allowSleep) {
97  //PRT_DEBUG("RT: want to sleep " << sleep << "us");
98  sleep += static_cast<int64_t>(sleepAdjust);
99  int64_t delta = 0;
100  if (sleep > 0) {
101  //PRT_DEBUG("RT: Sleeping for " << sleep << "us");
102  Timer::sleep(sleep);
103  int64_t slept = Timer::getTime() - currentRealTime;
104  //PRT_DEBUG("RT: Realy slept for " << slept << "us");
105  delta = sleep - slept;
106  }
107  const double ALPHA = 0.2;
108  sleepAdjust = sleepAdjust * (1 - ALPHA) + delta * ALPHA;
109  //PRT_DEBUG("RT: SleepAdjust: " << sleepAdjust);
110  }
111  if (-sleep > MAX_LAG) {
112  idealRealTime = currentRealTime - MAX_LAG / 2;
113  }
114  }
115  if (allowSleep) {
116  eventDelay.sync(time);
117  }
118 
119  emuTime = time;
120 }
121 
122 void RealTime::executeUntil(EmuTime::param time, int /*userData*/)
123 {
124  internalSync(time, true);
126 }
127 
128 int RealTime::signalEvent(const std::shared_ptr<const Event>& event)
129 {
130  if (!motherBoard.isActive() || !enabled) {
131  // these are global events, only the active machine should
132  // synchronize with real time
133  return 0;
134  }
135  if (event->getType() == OPENMSX_FINISH_FRAME_EVENT) {
136  auto& ffe = checked_cast<const FinishFrameEvent&>(*event);
137  if (!ffe.needRender()) {
138  // sync but don't sleep
139  sync(getCurrentTime(), false);
140  }
141  } else if (event->getType() == OPENMSX_FRAME_DRAWN_EVENT) {
142  // sync and possibly sleep
143  sync(getCurrentTime(), true);
144  }
145  return 0;
146 }
147 
148 void RealTime::update(const Setting& /*setting*/)
149 {
150  resync();
151 }
152 
153 void RealTime::update(const ThrottleManager& /*throttleManager*/)
154 {
155  resync();
156 }
157 
159 {
160  if (!enabled) return;
161 
162  idealRealTime = Timer::getTime();
163  sleepAdjust = 0.0;
164  removeSyncPoint();
165  emuTime = getCurrentTime();
167 }
168 
170 {
171  enabled = true;
172  resync();
173 }
174 
176 {
177  enabled = false;
178  removeSyncPoint();
179 }
180 
181 } // namespace openmsx