openMSX
Alarm.cc
Go to the documentation of this file.
1 #include "Alarm.hh"
2 #include "Timer.hh"
3 #include "Semaphore.hh"
4 #include "MSXException.hh"
5 #include "StringOp.hh"
6 #include <algorithm>
7 #include <vector>
8 #include <cassert>
9 #include <limits>
10 #include <SDL.h>
11 
31 namespace openmsx {
32 
34 {
35 public:
36  static AlarmManager& instance();
37  void registerAlarm(Alarm& alarm);
38  void unregisterAlarm(Alarm& alarm);
39  void start(Alarm& alarm, unsigned newPeriod);
40  void stop(Alarm& alarm);
41  bool isPending(const Alarm& alarm);
42 
43 private:
44  AlarmManager();
45  ~AlarmManager();
46 
47  static unsigned timerCallback(unsigned interval, void* param);
48  unsigned timerCallback2();
49 
50  std::vector<Alarm*> alarms;
51  int64_t time;
52  SDL_TimerID id;
53  Semaphore sem;
54  static volatile bool enabled;
55 };
56 
57 
58 // to minimize (or fix completely?) the race condition on exit
59 volatile bool AlarmManager::enabled = false;
60 
61 AlarmManager::AlarmManager()
62  : id(nullptr), sem(1)
63 {
64  if (SDL_Init(SDL_INIT_TIMER) < 0) {
65  throw FatalError(StringOp::Builder() <<
66  "Couldn't initialize SDL timer subsystem" <<
67  SDL_GetError());
68  }
69  enabled = true;
70 }
71 
72 AlarmManager::~AlarmManager()
73 {
74  assert(alarms.empty());
75  enabled = false;
76  if (id) {
77  SDL_RemoveTimer(id);
78  }
79 }
80 
82 {
83  static AlarmManager oneInstance;
84  return oneInstance;
85 }
86 
88 {
89  ScopedLock lock(sem);
90  assert(find(alarms.begin(), alarms.end(), &alarm) == alarms.end());
91  alarms.push_back(&alarm);
92 }
93 
95 {
96  ScopedLock lock(sem);
97  auto it = find(alarms.begin(), alarms.end(), &alarm);
98  assert(it != alarms.end());
99  alarms.erase(it);
100 }
101 
102 static int convert(int period)
103 {
104  return std::max(1, period / 1000);
105 }
106 
107 void AlarmManager::start(Alarm& alarm, unsigned period)
108 {
109  ScopedLock lock(sem);
110  alarm.period = period;
111  alarm.time = Timer::getTime() + period;
112  alarm.active = true;
113 
114  if (id) {
115  // there already is a timer
116  int64_t diff = time - alarm.time;
117  if (diff <= 0) {
118  // but we already have an earlier timer, do nothing
119  } else {
120  // new timer is earlier
121  SDL_RemoveTimer(id);
122  time = alarm.time;
123  id = SDL_AddTimer(convert(period), timerCallback, this);
124  }
125  } else {
126  // no timer yet
127  time = alarm.time;
128  id = SDL_AddTimer(convert(period), timerCallback, this);
129  }
130 }
131 
133 {
134  ScopedLock lock(sem);
135  alarm.active = false;
136  // No need to remove timer, we can handle spurious callbacks.
137  // Maybe in the future remove it as an optimization?
138 }
139 
140 bool AlarmManager::isPending(const Alarm& alarm)
141 {
142  ScopedLock lock(sem);
143  return alarm.active;
144 }
145 
146 unsigned AlarmManager::timerCallback(unsigned /*interval*/, void* param)
147 {
148  // note: runs in a different thread!
149  if (!enabled) return 0;
150  auto manager = static_cast<AlarmManager*>(param);
151  return manager->timerCallback2();
152 }
153 
154 unsigned AlarmManager::timerCallback2()
155 {
156  // note: runs in a different thread!
157  ScopedLock lock(sem);
158 
159  int64_t now = Timer::getTime();
160  int64_t earliest = std::numeric_limits<int64_t>::max();
161  for (auto& a : alarms) {
162  if (a->active) {
163  // timer active
164  // note: don't compare time directly (a->time < now),
165  // because there is a small chance time will wrap
166  int64_t left = a->time - now;
167  if (left <= 0) {
168  // timer expired
169  if (a->alarm()) {
170  // repeat
171  a->time += a->period;
172  left = a->time - now;
173  // 'left' can still be negative at this
174  // point, but that's ok .. convert()
175  // will return '1' in that case
176  earliest = std::min(earliest, left);
177  } else {
178  a->active = false;
179  }
180  } else {
181  // timer active but not yet expired
182  earliest = std::min(earliest, left);
183  }
184  }
185  }
186  if (earliest != std::numeric_limits<int64_t>::max()) {
187  time = earliest + now;
188  assert(id);
189  return convert(int(earliest));
190  } else {
191  for (auto& a : alarms) {
192  assert(a->active == false); (void)a;
193  }
194  id = nullptr;
195  return 0; // don't repeat
196  }
197 }
198 
199 
200 // class Alarm
201 
203  : manager(AlarmManager::instance())
204  , active(false)
205  , destructing(false)
206 {
207  manager.registerAlarm(*this);
208 }
209 
211 {
212  assert(destructing); // prepareDelete() must be called in subclass
213 }
214 
216 {
217  assert(!destructing); // not yet called
218  manager.unregisterAlarm(*this);
219  destructing = true;
220 }
221 
222 void Alarm::schedule(unsigned newPeriod)
223 {
224  manager.start(*this, newPeriod);
225 }
226 
228 {
229  manager.stop(*this);
230 }
231 
232 bool Alarm::pending() const
233 {
234  return manager.isPending(*this);
235 }
236 
237 } // namespace openmsx
bool isPending(const Alarm &alarm)
Definition: Alarm.cc:140
void cancel()
Cancel a previous schedule() request.
Definition: Alarm.cc:227
bool pending() const
Is there a pending alarm?
Definition: Alarm.cc:232
void registerAlarm(Alarm &alarm)
Definition: Alarm.cc:87
static AlarmManager & instance()
Definition: Alarm.cc:81
void prepareDelete()
Concrete subclasses MUST call this method in their destructor.
Definition: Alarm.cc:215
void unregisterAlarm(Alarm &alarm)
Definition: Alarm.cc:94
void start(Alarm &alarm, unsigned newPeriod)
Definition: Alarm.cc:107
void convert(const th_ycbcr_buffer &input, RawFrame &output)
Definition: yuv2rgb.cc:341
virtual ~Alarm()
Definition: Alarm.cc:210
void stop(Alarm &alarm)
Definition: Alarm.cc:132
void schedule(unsigned period)
Arrange for the alarm() method to be called after some time.
Definition: Alarm.cc:222
uint64_t getTime()
Get current (real) time in us.
Definition: Timer.cc:24