Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2025 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 :
23 : #include "timer.h"
24 :
25 : #include <condition_variable>
26 : #include <functional>
27 : #include <queue>
28 : #include <thread>
29 : #include <atomic>
30 : #include <mutex>
31 :
32 : namespace muse {
33 : class TimerScheduler
34 : {
35 : public:
36 : using Clock = std::chrono::steady_clock;
37 : using TimePoint = Clock::time_point;
38 : using Duration = Clock::duration;
39 : using Callback = std::function<void ()>;
40 :
41 0 : static TimerScheduler& instance()
42 : {
43 50 : static TimerScheduler scheduler;
44 0 : return scheduler;
45 : }
46 :
47 : struct TimerHandle {
48 : std::atomic_bool active { true };
49 : };
50 :
51 : using TimerHandlePtr = std::shared_ptr<TimerHandle>;
52 :
53 0 : TimerHandlePtr schedule(Duration delay, Callback callback, bool repeat = false)
54 : {
55 0 : auto handle = std::make_shared<TimerHandle>();
56 :
57 : {
58 0 : std::lock_guard lock(m_mutex);
59 0 : m_queue.push({
60 0 : Clock::now() + delay,
61 : delay,
62 : repeat,
63 0 : std::move(callback),
64 : handle
65 : });
66 0 : }
67 :
68 0 : m_cv.notify_one();
69 0 : return handle;
70 0 : }
71 :
72 0 : void cancel(const TimerHandlePtr& handle)
73 : {
74 0 : if (handle) {
75 0 : handle->active = false;
76 : }
77 0 : m_cv.notify_one();
78 0 : }
79 :
80 : private:
81 0 : TimerScheduler()
82 0 : : m_running(true),
83 0 : m_thread([this] { run(); })
84 0 : {}
85 :
86 0 : ~TimerScheduler()
87 : {
88 : {
89 0 : std::lock_guard lock(m_mutex);
90 0 : m_running = false;
91 0 : }
92 0 : m_cv.notify_one();
93 0 : m_thread.join();
94 0 : }
95 :
96 : struct Entry {
97 : TimePoint time;
98 : Duration interval;
99 : bool repeat = false;
100 : Callback callback;
101 : TimerHandlePtr handle;
102 :
103 0 : bool operator>(const Entry& other) const
104 : {
105 0 : return time > other.time;
106 : }
107 : };
108 :
109 0 : void run()
110 : {
111 0 : std::unique_lock lock(m_mutex);
112 :
113 0 : while (m_running) {
114 0 : if (m_queue.empty()) {
115 0 : m_cv.wait(lock);
116 0 : continue;
117 : }
118 :
119 0 : auto& next = m_queue.top();
120 0 : if (m_cv.wait_until(lock, next.time) == std::cv_status::timeout) {
121 0 : auto entry = m_queue.top();
122 0 : m_queue.pop();
123 :
124 0 : if (entry.handle->active) {
125 0 : lock.unlock();
126 0 : entry.callback();
127 0 : lock.lock();
128 :
129 0 : if (entry.repeat && entry.handle->active) {
130 0 : entry.time = Clock::now() + entry.interval;
131 0 : m_queue.push(entry);
132 : }
133 : }
134 0 : }
135 : }
136 0 : }
137 :
138 : std::priority_queue<Entry,
139 : std::vector<Entry>,
140 : std::greater<> > m_queue;
141 :
142 : bool m_running = false;
143 : std::mutex m_mutex;
144 : std::condition_variable m_cv;
145 : std::thread m_thread;
146 : };
147 : }
148 :
149 : using namespace muse;
150 :
151 0 : Timer::Timer(time_t interval)
152 0 : : m_interval(interval) {}
153 :
154 0 : Timer::~Timer()
155 : {
156 0 : stop();
157 0 : }
158 :
159 0 : void Timer::onTimeout(const async::Asyncable* receiver, Callback callback)
160 : {
161 0 : m_notification.onNotify(receiver, std::move(callback), async::Asyncable::Mode::SetReplace);
162 0 : }
163 :
164 0 : void Timer::start()
165 : {
166 0 : if (m_handle) {
167 0 : return;
168 : }
169 :
170 0 : m_started = std::chrono::steady_clock::now();
171 :
172 0 : auto realHandle = TimerScheduler::instance().schedule(
173 0 : m_interval,
174 0 : [this]() { m_notification.notify(); },
175 : true
176 0 : );
177 :
178 0 : m_handle = std::static_pointer_cast<void>(realHandle);
179 0 : }
180 :
181 0 : void Timer::stop()
182 : {
183 0 : if (!m_handle) {
184 0 : return;
185 : }
186 :
187 0 : auto realHandle = std::static_pointer_cast<TimerScheduler::TimerHandle>(m_handle);
188 0 : TimerScheduler::instance().cancel(realHandle);
189 0 : m_handle = nullptr;
190 0 : }
191 :
192 0 : bool Timer::isActive() const
193 : {
194 0 : return m_handle != nullptr;
195 : }
196 :
197 0 : float Timer::secondsSinceStart() const
198 : {
199 0 : std::chrono::duration<float, std::ratio<1> > diff(std::chrono::steady_clock::now() - m_started);
200 0 : return diff.count();
201 : }
|