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