Process audio slider events asynchronously

This commit avoids piling up events if events are queued quicker, than
gBar can handle, causing severe slowdowns.
Instead, the requests are handled in a worker thread and excess events
are discarded.

Fixes: https://github.com/scorpion-26/gBar/issues/86
This commit is contained in:
scorpion-26 2024-04-03 16:11:36 +02:00
parent 0f943405d3
commit 6dd1ee6783
2 changed files with 85 additions and 4 deletions

View file

@ -288,14 +288,30 @@ namespace Bar
Widget* micSlider; Widget* micSlider;
Button* audioIcon; Button* audioIcon;
Button* micIcon; Button* micIcon;
static AsyncAtomicContext<double> sinkAsyncContext;
void OnChangeVolumeSink(Slider&, double value) void OnChangeVolumeSink(Slider&, double value)
{ {
System::SetVolumeSink(value); // Process async and atomically, so the event handler isn't filled up
ExecuteAsyncAtomically(
sinkAsyncContext,
[](double volume)
{
System::SetVolumeSink(volume);
},
value);
} }
static AsyncAtomicContext<double> sourceAsyncContext;
void OnChangeVolumeSource(Slider&, double value) void OnChangeVolumeSource(Slider&, double value)
{ {
System::SetVolumeSource(value); ExecuteAsyncAtomically(
sourceAsyncContext,
[](double volume)
{
System::SetVolumeSource(volume);
},
value);
} }
// For text // For text
@ -304,7 +320,14 @@ namespace Bar
{ {
audioVolume += delta; audioVolume += delta;
audioVolume = std::clamp(audioVolume, 0.0, 1.0); audioVolume = std::clamp(audioVolume, 0.0, 1.0);
System::SetVolumeSink(audioVolume); // Process async and atomically, so the event handler isn't filled up
ExecuteAsyncAtomically(
sinkAsyncContext,
[](double volume)
{
System::SetVolumeSink(volume);
},
audioVolume);
} }
double micVolume = 0; double micVolume = 0;
@ -312,7 +335,14 @@ namespace Bar
{ {
micVolume += delta; micVolume += delta;
micVolume = std::clamp(micVolume, 0.0, 1.0); micVolume = std::clamp(micVolume, 0.0, 1.0);
System::SetVolumeSource(micVolume); // Process async and atomically, so the event handler isn't filled up
ExecuteAsyncAtomically(
sourceAsyncContext,
[](double volume)
{
System::SetVolumeSource(volume);
},
micVolume);
} }
void OnToggleSink(Button& button) void OnToggleSink(Button& button)

View file

@ -1,11 +1,15 @@
#pragma once #pragma once
#include <atomic>
#include <iostream> #include <iostream>
#include <optional>
#include <thread>
#include <unistd.h> #include <unistd.h>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <filesystem> #include <filesystem>
#include <map> #include <map>
#include <mutex>
#include "Log.h" #include "Log.h"
@ -186,6 +190,53 @@ inline Process OpenProcess(const std::string& command)
} }
} }
template<typename Data>
struct AsyncAtomicContext
{
std::atomic<bool> running = false;
std::mutex queueLock;
std::optional<Data> queue;
};
// Executes the callback function asynchronously, but only one at a time.
// Multiple requests at once are stored in a FIFO of size 1.
// The context should point to a static reference.
template<typename Data, typename Callback>
inline void ExecuteAsyncAtomically(AsyncAtomicContext<Data>& context, const Callback& callback, const Data& data)
{
// Update the queue
context.queueLock.lock();
context.queue = data;
context.queueLock.unlock();
if (!context.running)
{
// Launch the thread
context.running = true;
std::thread(
[&, callback]()
{
while (true)
{
context.queueLock.lock();
if (!context.queue.has_value())
{
context.queueLock.unlock();
break;
}
Data data = std::move(*context.queue);
context.queue = {};
context.queueLock.unlock();
// Execute
callback(std::move(data));
}
context.running = false;
})
.detach();
}
}
// Plugins // Plugins
#include "Window.h" #include "Window.h"
#define DL_VERSION 1 #define DL_VERSION 1