The Battle for Wesnoth  1.15.1+dev
timer.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2018 by Mark de Wever <koraq@xs4all.nl>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #include "gui/core/timer.hpp"
16 
17 #include "events.hpp"
18 #include "gui/core/log.hpp"
19 
20 #include <SDL2/SDL_timer.h>
21 
22 #include <map>
23 #include <mutex>
24 
25 namespace gui2
26 {
27 
28 struct timer
29 {
30  timer() : sdl_id(0), interval(0), callback()
31  {
32  }
33 
34  SDL_TimerID sdl_id;
35  uint32_t interval;
36  std::function<void(std::size_t id)> callback;
37 };
38 
39 /** Ids for the timers. */
40 static std::size_t next_timer_id = 0;
41 
42 /** The active timers. */
43 static std::map<std::size_t, timer>& get_timers()
44 {
45  static std::map<std::size_t, timer>* ptimers = new std::map<std::size_t, timer>();
46  return *ptimers;
47 }
48 /**
49  The id of the event being executed, 0 if none.
50  NOTE: it is possible that multiple timers are executed at the same time
51  if one of the timer starts an event loop for example if its handler
52  shows a dialog. In that case code that relies on this breaks. This
53  could probably fixed my making this a list/stack of ids.
54 */
55 static std::size_t executing_id = 0;
56 
57 std::mutex timers_mutex;
58 
59 /** Did somebody try to remove the timer during its execution? */
60 static bool executing_id_removed = false;
61 
62 /**
63  * Helper to make removing a timer in a callback safe.
64  *
65  * Upon creation it sets the executing id and clears the remove request flag.
66  *
67  * If an remove_timer() is called for the id being executed it requests a
68  * remove the timer and exits remove_timer().
69  *
70  * Upon destruction it tests whether there was a request to remove the id and
71  * does so. It also clears the executing id. It leaves the remove request flag
72  * since the execution function needs to know whether or not the event was
73  * removed.
74  */
75 class executor
76 {
77 public:
78  executor(std::size_t id)
79  {
80  executing_id = id;
81  executing_id_removed = false;
82  }
83 
85  {
86  const std::size_t id = executing_id;
87  executing_id = 0;
88  if(executing_id_removed) {
89  remove_timer(id);
90  }
91  }
92 };
93 
94 extern "C" {
95 
96 static uint32_t timer_callback(uint32_t, void* id)
97 {
98  DBG_GUI_E << "Pushing timer event in queue.\n";
99  // iTunes still reports a couple of crashes here. Cannot see a problem yet.
100 
101  Uint32 result;
102  {
103  std::lock_guard<std::mutex> lock(timers_mutex);
104 
105  auto itor = get_timers().find(reinterpret_cast<std::size_t>(id));
106  if(itor == get_timers().end()) {
107  return 0;
108  }
109  result = itor->second.interval;
110  }
111 
112  SDL_Event event;
113 
114  event.type = TIMER_EVENT;
115  event.user.code = 0;
116  event.user.data1 = id;
117  event.user.data2 = nullptr;
118 
119  SDL_PushEvent(&event);
120 
121  return result;
122 }
123 
124 } // extern "C"
125 
126 std::size_t add_timer(const uint32_t interval,
127  const std::function<void(std::size_t id)>& callback,
128  const bool repeat)
129 {
130  static_assert(sizeof(std::size_t) == sizeof(void*), "Pointer and std::size_t are not the same size");
131 
132  DBG_GUI_E << "Adding timer.\n";
133 
134  timer timer;
135  {
136  std::lock_guard<std::mutex> lock(timers_mutex);
137 
138  do {
139  ++next_timer_id;
140  } while(next_timer_id == 0 || get_timers().count(next_timer_id) > 0);
141 
142  timer.sdl_id = SDL_AddTimer(
143  interval, timer_callback, reinterpret_cast<void*>(next_timer_id));
144  }
145 
146  if(timer.sdl_id == 0) {
147  WRN_GUI_E << "Failed to create an sdl timer." << std::endl;
148  return 0;
149  }
150 
151  if(repeat) {
152  timer.interval = interval;
153  }
154 
155  timer.callback = callback;
156 
157  {
158  std::lock_guard<std::mutex> lock(timers_mutex);
159 
160  get_timers().emplace(next_timer_id, timer);
161  }
162 
163  DBG_GUI_E << "Added timer " << next_timer_id << ".\n";
164  return next_timer_id;
165 }
166 
167 bool remove_timer(const std::size_t id)
168 {
169  DBG_GUI_E << "Removing timer " << id << ".\n";
170 
171  std::lock_guard<std::mutex> lock(timers_mutex);
172 
173  auto itor = get_timers().find(id);
174  if(itor == get_timers().end()) {
175  LOG_GUI_E << "Can't remove timer since it no longer exists.\n";
176  return false;
177  }
178 
179  if(id == executing_id) {
180  executing_id_removed = true;
181  return true;
182  }
183 
184  if(!SDL_RemoveTimer(itor->second.sdl_id)) {
185  /*
186  * This can happen if the caller of the timer didn't get the event yet
187  * but the timer has already been fired. This due to the fact that a
188  * timer pushes an event in the queue, which allows the following
189  * condition:
190  * - Timer fires
191  * - Push event in queue
192  * - Another event is processed and tries to remove the event.
193  */
194  DBG_GUI_E << "The timer is already out of the SDL timer list.\n";
195  }
196  get_timers().erase(itor);
197  return true;
198 }
199 
200 bool execute_timer(const std::size_t id)
201 {
202  DBG_GUI_E << "Executing timer " << id << ".\n";
203 
204  std::function<void(size_t)> callback = nullptr;
205  {
206  std::lock_guard<std::mutex> lock(timers_mutex);
207 
209  if(itor == get_timers().end()) {
210  LOG_GUI_E << "Can't execute timer since it no longer exists.\n";
211  return false;
212  }
213 
214  callback = itor->second.callback;
215 
216  if(itor->second.interval == 0) {
217  get_timers().erase(itor);
218  }
219  }
220 
221  callback(id);
222 
223  return true;
224 }
225 
226 } // namespace gui2
Define the common log macros for the gui toolkit.
static bool executing_id_removed
Did somebody try to remove the timer during its execution?
Definition: timer.cpp:60
#define LOG_GUI_E
Definition: log.hpp:35
std::mutex timers_mutex
Definition: timer.cpp:57
static std::size_t next_timer_id
Ids for the timers.
Definition: timer.cpp:40
#define TIMER_EVENT
Definition: events.hpp:24
executor(std::size_t id)
Definition: timer.cpp:78
static uint32_t timer_callback(uint32_t, void *id)
Definition: timer.cpp:96
std::function< void(std::size_t id)> callback
Definition: timer.cpp:36
Generic file dialog.
Definition: field-fwd.hpp:22
static std::size_t executing_id
The id of the event being executed, 0 if none.
Definition: timer.cpp:55
#define DBG_GUI_E
Definition: log.hpp:34
#define WRN_GUI_E
Definition: log.hpp:36
uint32_t interval
Definition: timer.cpp:35
Contains the gui2 timer routines.
std::size_t add_timer(const uint32_t interval, const std::function< void(std::size_t id)> &callback, const bool repeat)
Adds a new timer.
Definition: timer.cpp:126
Helper to make removing a timer in a callback safe.
Definition: timer.cpp:75
SDL_TimerID sdl_id
Definition: timer.cpp:34
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
bool execute_timer(const std::size_t id)
Executes a timer.
Definition: timer.cpp:200
static std::map< std::size_t, timer > & get_timers()
The active timers.
Definition: timer.cpp:43
bool remove_timer(const std::size_t id)
Removes a timer.
Definition: timer.cpp:167