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