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