Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
port_change_watcher.hpp
Go to the documentation of this file.
1#pragma once
2
11
12#include <pulp/midi/device.hpp>
13
14#include <chrono>
15#include <condition_variable>
16#include <functional>
17#include <memory>
18#include <mutex>
19#include <string>
20#include <thread>
21#include <utility>
22#include <vector>
23
24namespace pulp::midi {
25
28 std::vector<MidiPortInfo> added;
29 std::vector<MidiPortInfo> removed;
30 bool empty() const {
31 return added.empty() && removed.empty();
32 }
33};
34
35namespace detail {
36inline bool same_port(const MidiPortInfo& a, const MidiPortInfo& b) {
37 return a.id == b.id && a.name == b.name && a.is_input == b.is_input &&
38 a.is_output == b.is_output;
39}
40inline bool contains_port(const std::vector<MidiPortInfo>& list, const MidiPortInfo& p) {
41 for (const auto& q : list) {
42 if (same_port(p, q))
43 return true;
44 }
45 return false;
46}
47} // namespace detail
48
54inline PortListDiff diff_port_lists(const std::vector<MidiPortInfo>& before,
55 const std::vector<MidiPortInfo>& after) {
57 std::vector<bool> matched(after.size(), false);
58 for (const auto& b : before) {
59 bool found = false;
60 for (std::size_t i = 0; i < after.size(); ++i) {
61 if (!matched[i] && detail::same_port(b, after[i])) {
62 matched[i] = true;
63 found = true;
64 break;
65 }
66 }
67 if (!found)
68 d.removed.push_back(b);
69 }
70 for (std::size_t i = 0; i < after.size(); ++i) {
71 if (!matched[i])
72 d.added.push_back(after[i]);
73 }
74 return d;
75}
76
79 public:
82 PortListDiff update(std::vector<MidiPortInfo> current) {
84 if (have_baseline_)
85 d = diff_port_lists(last_, current);
86 last_ = std::move(current);
87 have_baseline_ = true;
88 return d;
89 }
90
91 private:
92 std::vector<MidiPortInfo> last_;
93 bool have_baseline_ = false;
94};
95
102 public:
103 using Enumerate = std::function<std::vector<MidiPortInfo>()>;
104 using OnChange = std::function<void(const PortListDiff&)>;
105
106 PortChangePoller() = default;
110 stop();
111 }
112
116 void start(Enumerate enumerate, OnChange on_change,
117 std::chrono::milliseconds interval = std::chrono::milliseconds(1000)) {
118 // A change handler that re-registers from the worker thread cannot
119 // restart its own thread; ignore the request.
120 if (worker_.joinable() && worker_.get_id() == std::this_thread::get_id())
121 return;
122 stop();
123 // The worker owns this state through a shared pointer and never
124 // touches the poller itself, so it cannot outlive what it uses.
125 auto state = std::make_shared<State>();
126 state->tracker.update(enumerate());
127 state_ = state;
128 worker_ = std::thread(
129 [state, enumerate = std::move(enumerate), on_change = std::move(on_change), interval] {
130 std::unique_lock<std::mutex> lk(state->mu);
131 while (!state->stopping) {
132 state->cv.wait_for(lk, interval, [&state] { return state->stopping; });
133 if (state->stopping)
134 break;
135 lk.unlock();
136 const auto diff = state->tracker.update(enumerate());
137 if (!diff.empty() && on_change)
138 on_change(diff);
139 lk.lock();
140 }
141 });
142 }
143
144 void stop() {
145 if (auto state = state_) {
146 {
147 std::lock_guard<std::mutex> lk(state->mu);
148 state->stopping = true;
149 }
150 state->cv.notify_all();
151 }
152 // Called from inside `on_change`: the loop sees the flag and exits on
153 // its own; the next stop() or the destructor joins it.
154 if (worker_.joinable() && worker_.get_id() != std::this_thread::get_id())
155 worker_.join();
156 }
157
158 bool running() const {
159 return worker_.joinable();
160 }
161
162 private:
163 struct State {
164 std::mutex mu;
165 std::condition_variable cv;
166 bool stopping = false;
167 PortListTracker tracker;
168 };
169
170 std::shared_ptr<State> state_;
171 std::thread worker_;
172};
173
174} // namespace pulp::midi
std::function< std::vector< MidiPortInfo >()> Enumerate
Definition port_change_watcher.hpp:103
void start(Enumerate enumerate, OnChange on_change, std::chrono::milliseconds interval=std::chrono::milliseconds(1000))
Start polling.
Definition port_change_watcher.hpp:116
bool running() const
Definition port_change_watcher.hpp:158
PortChangePoller & operator=(const PortChangePoller &)=delete
void stop()
Definition port_change_watcher.hpp:144
PortChangePoller(const PortChangePoller &)=delete
~PortChangePoller()
Definition port_change_watcher.hpp:109
std::function< void(const PortListDiff &)> OnChange
Definition port_change_watcher.hpp:104
Remembers the last snapshot and reports what changed since.
Definition port_change_watcher.hpp:78
PortListDiff update(std::vector< MidiPortInfo > current)
Record current and return the difference from the previous snapshot.
Definition port_change_watcher.hpp:82
Definition port_change_watcher.hpp:35
bool contains_port(const std::vector< MidiPortInfo > &list, const MidiPortInfo &p)
Definition port_change_watcher.hpp:40
bool same_port(const MidiPortInfo &a, const MidiPortInfo &b)
Definition port_change_watcher.hpp:36
Definition custom_node_events.hpp:26
PortListDiff diff_port_lists(const std::vector< MidiPortInfo > &before, const std::vector< MidiPortInfo > &after)
Compare two snapshots.
Definition port_change_watcher.hpp:54
SequencerStateChannel is the audio-safe transport for a plugin's structured NON-parameter state: the ...
Definition audio_device_manager.hpp:48
Definition device.hpp:11
Ports that appeared and ports that went away between two snapshots.
Definition port_change_watcher.hpp:27
std::vector< MidiPortInfo > added
Definition port_change_watcher.hpp:28
bool empty() const
Definition port_change_watcher.hpp:30
std::vector< MidiPortInfo > removed
Definition port_change_watcher.hpp:29