Skip to content

Commit 45a32db

Browse files
Merge pull request #31685 from RomanPudashkin/timer_scheduler
Fix #31605: refactor Timer to use shared TimerScheduler
2 parents 50207b8 + dcd8c53 commit 45a32db

File tree

4 files changed

+213
-59
lines changed

4 files changed

+213
-59
lines changed

src/framework/global/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ target_sources(muse_global PRIVATE
5252
runtime.h
5353
translation.cpp
5454
translation.h
55+
timer.cpp
5556
timer.h
5657
progress.h
5758
utils.cpp

src/framework/global/timer.cpp

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/*
2+
* SPDX-License-Identifier: GPL-3.0-only
3+
* MuseScore-CLA-applies
4+
*
5+
* MuseScore
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+
static TimerScheduler& instance()
42+
{
43+
static TimerScheduler scheduler;
44+
return scheduler;
45+
}
46+
47+
struct TimerHandle {
48+
std::atomic_bool active { true };
49+
};
50+
51+
using TimerHandlePtr = std::shared_ptr<TimerHandle>;
52+
53+
TimerHandlePtr schedule(Duration delay, Callback callback, bool repeat = false)
54+
{
55+
auto handle = std::make_shared<TimerHandle>();
56+
57+
{
58+
std::lock_guard lock(m_mutex);
59+
m_queue.push({
60+
Clock::now() + delay,
61+
delay,
62+
repeat,
63+
std::move(callback),
64+
handle
65+
});
66+
}
67+
68+
m_cv.notify_one();
69+
return handle;
70+
}
71+
72+
void cancel(const TimerHandlePtr& handle)
73+
{
74+
if (handle) {
75+
handle->active = false;
76+
}
77+
m_cv.notify_one();
78+
}
79+
80+
private:
81+
TimerScheduler()
82+
: m_running(true),
83+
m_thread([this] { run(); })
84+
{}
85+
86+
~TimerScheduler()
87+
{
88+
{
89+
std::lock_guard lock(m_mutex);
90+
m_running = false;
91+
}
92+
m_cv.notify_one();
93+
m_thread.join();
94+
}
95+
96+
struct Entry {
97+
TimePoint time;
98+
Duration interval;
99+
bool repeat = false;
100+
Callback callback;
101+
TimerHandlePtr handle;
102+
103+
bool operator>(const Entry& other) const
104+
{
105+
return time > other.time;
106+
}
107+
};
108+
109+
void run()
110+
{
111+
std::unique_lock lock(m_mutex);
112+
113+
while (m_running) {
114+
if (m_queue.empty()) {
115+
m_cv.wait(lock);
116+
continue;
117+
}
118+
119+
auto& next = m_queue.top();
120+
if (m_cv.wait_until(lock, next.time) == std::cv_status::timeout) {
121+
auto entry = m_queue.top();
122+
m_queue.pop();
123+
124+
if (entry.handle->active) {
125+
lock.unlock();
126+
entry.callback();
127+
lock.lock();
128+
129+
if (entry.repeat && entry.handle->active) {
130+
entry.time = Clock::now() + entry.interval;
131+
m_queue.push(entry);
132+
}
133+
}
134+
}
135+
}
136+
}
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+
Timer::Timer(time_t interval)
152+
: m_interval(interval) {}
153+
154+
Timer::~Timer()
155+
{
156+
stop();
157+
}
158+
159+
void Timer::onTimeout(const async::Asyncable* receiver, Callback callback)
160+
{
161+
m_notification.onNotify(receiver, std::move(callback), async::Asyncable::Mode::SetReplace);
162+
}
163+
164+
void Timer::start()
165+
{
166+
if (m_handle) {
167+
return;
168+
}
169+
170+
m_started = std::chrono::steady_clock::now();
171+
172+
auto realHandle = TimerScheduler::instance().schedule(
173+
m_interval,
174+
[this]() { m_notification.notify(); },
175+
true
176+
);
177+
178+
m_handle = std::static_pointer_cast<void>(realHandle);
179+
}
180+
181+
void Timer::stop()
182+
{
183+
if (!m_handle) {
184+
return;
185+
}
186+
187+
auto realHandle = std::static_pointer_cast<TimerScheduler::TimerHandle>(m_handle);
188+
TimerScheduler::instance().cancel(realHandle);
189+
m_handle = nullptr;
190+
}
191+
192+
bool Timer::isActive() const
193+
{
194+
return m_handle != nullptr;
195+
}
196+
197+
float Timer::secondsSinceStart() const
198+
{
199+
std::chrono::duration<float, std::ratio<1> > diff(std::chrono::steady_clock::now() - m_started);
200+
return diff.count();
201+
}

src/framework/global/timer.h

Lines changed: 10 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
#include "async/asyncable.h"
2626

2727
#include <chrono>
28-
#include <thread>
2928

3029
namespace muse {
3130
/*!
@@ -40,73 +39,25 @@ class Timer
4039
public:
4140
using time_t = std::chrono::microseconds;
4241

43-
explicit Timer(time_t interval)
44-
: m_interval(interval) {}
45-
46-
~Timer()
47-
{
48-
stop();
49-
50-
if (m_thread.joinable()) {
51-
m_thread.join();
52-
}
53-
}
42+
explicit Timer(time_t interval);
43+
~Timer();
5444

5545
Timer(const Timer&) = delete;
5646
Timer& operator=(const Timer&) = delete;
5747

58-
template<typename Func>
59-
void onTimeout(const async::Asyncable* receiver, Func function)
60-
{
61-
m_notification.onNotify(receiver, function, async::Asyncable::Mode::SetReplace);
62-
}
63-
64-
void start()
65-
{
66-
if (m_active) {
67-
return;
68-
}
69-
70-
m_active = true;
71-
m_started = std::chrono::steady_clock::now();
72-
m_thread = std::thread([this]() { timerLoop(); });
73-
}
48+
using Callback = std::function<void ()>;
49+
void onTimeout(const async::Asyncable* receiver, Callback callback);
7450

75-
void stop()
76-
{
77-
m_active = false;
78-
}
51+
void start();
52+
void stop();
7953

80-
bool isActive() const
81-
{
82-
return m_active;
83-
}
84-
85-
float secondsSinceStart() const
86-
{
87-
std::chrono::duration<float, std::ratio<1> > diff(std::chrono::steady_clock::now() - m_started);
88-
return diff.count();
89-
}
54+
bool isActive() const;
55+
float secondsSinceStart() const;
9056

9157
private:
92-
void timerLoop()
93-
{
94-
std::this_thread::sleep_for(m_interval);
95-
while (m_active) {
96-
auto start = std::chrono::steady_clock::now();
97-
m_notification.notify();
98-
auto end = std::chrono::steady_clock::now();
99-
if (m_interval > (end - start)) {
100-
auto diff = m_interval - (end - start);
101-
std::this_thread::sleep_for(diff);
102-
}
103-
}
104-
}
105-
10658
time_t m_interval;
107-
std::chrono::time_point<std::chrono::steady_clock> m_started;
108-
std::atomic_bool m_active { false };
10959
async::Notification m_notification;
110-
std::thread m_thread;
60+
std::chrono::time_point<std::chrono::steady_clock> m_started;
61+
std::shared_ptr<void> m_handle;
11162
};
11263
}

src/framework/musesampler/internal/musesamplerwrapper.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,7 @@ void MuseSamplerWrapper::prepareToPlay()
634634
doCurrentSetPosition();
635635

636636
if (readyToPlay()) {
637+
m_checkReadyToPlayTimer.reset();
637638
return;
638639
}
639640

0 commit comments

Comments
 (0)