MayaFlux 0.2.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
InputManager.cpp
Go to the documentation of this file.
1#include "InputManager.hpp"
2
5
8
9namespace MayaFlux::Core {
10
12#ifdef MAYAFLUX_PLATFORM_MACOS
13 : m_registrations(new RegistrationList())
14#else
15 : m_registrations(std::make_shared<const RegistrationList>())
16#endif
17{
18#ifdef MAYAFLUX_PLATFORM_MACOS
19 for (auto& hp : m_hazard_ptrs) {
20 hp.store(nullptr);
21 }
22#endif
23
25 "InputManager created");
26}
27
29{
30 if (m_running.load()) {
31 stop();
32 }
33
34#ifdef MAYAFLUX_PLATFORM_MACOS
35 delete m_registrations.load();
36#endif
37}
38
39// ─────────────────────────────────────────────────────────────────────────────
40// Lifecycle
41// ─────────────────────────────────────────────────────────────────────────────
42
44{
45 if (m_running.load()) {
47 "InputManager already running");
48 return;
49 }
50
51 if (!m_input_service) {
54
55 if (!m_input_service) {
57 "InputManager requires InputService but service not registered");
58 return;
59 }
60 }
61
62 m_stop_requested.store(false);
63 m_running.store(true);
65
67 "InputManager started");
68}
69
71{
72 if (!m_running.load()) {
73 return;
74 }
75
76 m_stop_requested.store(true);
77
78 m_queue_notify.store(true);
79 m_queue_notify.notify_one();
80
81 if (m_processing_thread.joinable()) {
83 }
84
85 m_running.store(false);
86
88 "InputManager stopped (processed {} events)", m_events_processed.load());
89}
90
91// ─────────────────────────────────────────────────────────────────────────────
92// Input Enqueueing
93// ─────────────────────────────────────────────────────────────────────────────
94
96{
97
98 if (!m_queue.push(value)) {
100 "Input queue full, dropping oldest event");
101 }
102
103 m_queue_notify.store(true);
104 m_queue_notify.notify_one();
105}
106
107void InputManager::enqueue_batch(const std::vector<InputValue>& values)
108{
109 if (values.empty())
110 return;
111
112 bool any_pushed = false;
113 for (const auto& value : values) {
114 if (m_queue.push(value)) {
115 any_pushed = true;
116 } else {
118 "Input queue full during batch, dropping oldest events");
119 }
120 }
121
122 if (any_pushed) {
123 m_queue_notify.store(true);
124 m_queue_notify.notify_one();
125 }
126}
127
128// ─────────────────────────────────────────────────────────────────────────────
129// Node Registration
130// ─────────────────────────────────────────────────────────────────────────────
131
133 const std::shared_ptr<Nodes::Input::InputNode>& node,
134 InputBinding binding)
135{
136 if (!node)
137 return;
138
139 if (binding.hid_vendor_id || binding.hid_product_id) {
140 if (!m_input_service) {
142 "VID/PID binding requires InputService but service not registered");
143 return;
144 }
145
146 auto devices = m_input_service->get_all_devices();
147 auto resolved = resolve_vid_pid(binding, devices);
148 if (resolved) {
149 binding = *resolved;
150 } else {
152 "No device found for VID/PID");
153 return;
154 }
155 }
156
157 if (binding.device_id != 0) {
158 if (!m_input_service) {
160 "Device ID binding requires InputService but service not registered");
161 return;
162 }
163
164 m_input_service->open_device(binding.backend, binding.device_id);
165 }
166
167 {
168 std::lock_guard lock(m_registry_mutex);
169#ifdef MAYAFLUX_PLATFORM_MACOS
170 auto* old_list = m_registrations.load();
171 auto* new_list = new RegistrationList(*old_list);
172 new_list->push_back({ .node = node, .binding = binding });
173 m_registrations.store(new_list);
174 retire_list(old_list);
175#else
176 auto current_list = m_registrations.load();
177 auto new_list = std::make_shared<RegistrationList>(*current_list);
178 new_list->push_back({ .node = node, .binding = binding });
179 m_registrations.store(new_list);
180#endif
181 m_tracked_nodes.push_back(node);
182 }
183
185 "Registered InputNode for backend {} device {}",
186 static_cast<int>(binding.backend), binding.device_id);
187}
188
189std::optional<InputBinding> InputManager::resolve_vid_pid(
190 const InputBinding& binding,
191 const std::vector<InputDeviceInfo>& devices) const
192{
193 for (const auto& dev : devices) {
194 if (dev.backend_type != binding.backend)
195 continue;
196
197 bool vid_match = !binding.hid_vendor_id || (*binding.hid_vendor_id == dev.vendor_id);
198 bool pid_match = !binding.hid_product_id || (*binding.hid_product_id == dev.product_id);
199
200 if (vid_match && pid_match) {
201 InputBinding resolved = binding;
202 resolved.device_id = dev.id;
203 resolved.hid_vendor_id.reset();
204 resolved.hid_product_id.reset();
205 return resolved;
206 }
207 }
208
209 return std::nullopt;
210}
211
212void InputManager::unregister_node(const std::shared_ptr<Nodes::Input::InputNode>& node)
213{
214 if (!node)
215 return;
216
217 {
218 std::lock_guard lock(m_registry_mutex);
219
220#ifdef MAYAFLUX_PLATFORM_MACOS
221 auto* old_list = m_registrations.load();
222 auto* new_list = new RegistrationList(*old_list);
223
224 new_list->erase(
225 std::remove_if(new_list->begin(), new_list->end(),
226 [&node](const NodeRegistration& reg) {
227 auto locked = reg.node.lock();
228 return !locked || locked == node;
229 }),
230 new_list->end());
231
232 m_registrations.store(new_list);
233 retire_list(old_list);
234#else
235 auto current_list = m_registrations.load();
236 auto new_list = std::make_shared<RegistrationList>(*current_list);
237
238 new_list->erase(
239 std::remove_if(new_list->begin(), new_list->end(),
240 [&node](const NodeRegistration& reg) {
241 auto locked = reg.node.lock();
242 return !locked || locked == node;
243 }),
244 new_list->end());
245
246 m_registrations.store(new_list);
247#endif
248 m_tracked_nodes.erase(
249 std::remove(m_tracked_nodes.begin(), m_tracked_nodes.end(), node),
250 m_tracked_nodes.end());
251 }
252
254 "Unregistered InputNode");
255}
256
258{
259 std::lock_guard lock(m_registry_mutex);
260
261#ifdef MAYAFLUX_PLATFORM_MACOS
262 auto* old_list = m_registrations.load();
264 retire_list(old_list);
265#else
266 m_registrations.store(std::make_shared<const RegistrationList>());
267#endif
268
269 m_tracked_nodes.clear();
270
272 "Unregistered all InputNodes (Registry swapped to empty)");
273}
274
276{
277#ifdef MAYAFLUX_PLATFORM_MACOS
278 // Brief hazard pointer acquisition for count
279 size_t slot = m_hazard_counter.fetch_add(1) % MAX_READERS;
280 const RegistrationList* current;
281 do {
282 current = m_registrations.load();
283 m_hazard_ptrs[slot].store(current);
284 } while (current != m_registrations.load());
285
286 size_t count = current->size();
287 m_hazard_ptrs[slot].store(nullptr);
288 return count;
289#else
290 return m_registrations.load()->size();
291#endif
292}
293
295{
296 return m_queue.snapshot().size();
297}
298
299// ─────────────────────────────────────────────────────────────────────────────
300// Processing Thread
301// ─────────────────────────────────────────────────────────────────────────────
302
304{
306 "Processing thread started");
307
308 while (true) {
309 InputValue value;
310
311 while (auto value = m_queue.pop()) {
312 dispatch_to_nodes(*value);
313 m_events_processed.fetch_add(1);
314 }
315
316 if (m_stop_requested.load()) {
317 break;
318 }
319
320 m_queue_notify.wait(false);
321 m_queue_notify.store(false);
322 }
323
325 "Processing thread exiting");
326}
327
329{
330#ifdef MAYAFLUX_PLATFORM_MACOS
331 // Acquire hazard pointer slot
332 size_t slot = m_hazard_counter.fetch_add(1) % MAX_READERS;
333
334 const RegistrationList* current_regs;
335 do {
336 current_regs = m_registrations.load();
337 m_hazard_ptrs[slot].store(current_regs);
338 } while (current_regs != m_registrations.load());
339
340 // Safe to use current_regs now
341 for (const auto& reg : *current_regs) {
342 auto node = reg.node.lock();
343 if (!node)
344 continue;
345
346 if (matches_binding(value, reg.binding)) {
347 node->process_input(value);
348 }
349 }
350
351 // Release hazard pointer
352 m_hazard_ptrs[slot].store(nullptr);
353#else
354 auto current_regs = m_registrations.load();
355
356 for (const auto& reg : *current_regs) {
357 auto node = reg.node.lock();
358 if (!node)
359 continue;
360
361 if (matches_binding(value, reg.binding)) {
362 node->process_input(value);
363 }
364 }
365#endif
366}
367
368bool InputManager::matches_binding(const InputValue& value, const InputBinding& binding) const
369{
370 if (binding.backend != value.source_type) {
371 return false;
372 }
373
374 if (binding.device_id != 0 && binding.device_id != value.device_id) {
375 return false;
376 }
377
378 switch (binding.backend) {
379 case InputType::MIDI:
380 if (value.type == InputValue::Type::MIDI) {
381 const auto& midi = value.as_midi();
382
383 if (binding.midi_channel && *binding.midi_channel != midi.channel()) {
384 return false;
385 }
386
387 if (binding.midi_message_type && *binding.midi_message_type != midi.type()) {
388 return false;
389 }
390 if (binding.midi_cc_number && midi.type() == 0xB0) {
391 if (*binding.midi_cc_number != midi.data1) {
392 return false;
393 }
394 }
395 }
396 break;
397
398 case InputType::OSC:
399 if (value.type == InputValue::Type::OSC && binding.osc_address_pattern) {
400 const auto& osc = value.as_osc();
401 if (!osc.address.starts_with(*binding.osc_address_pattern)) {
402 return false;
403 }
404 }
405 break;
406
407 default:
408 // HID, Serial: no additional filters beyond device_id
409 break;
410 }
411
412 return true;
413}
414
415#ifdef MAYAFLUX_PLATFORM_MACOS
416void InputManager::retire_list(const RegistrationList* list)
417{
418 // Check if any reader is using this list
419 for (const auto& hp : m_hazard_ptrs) {
420 if (hp.load() == list) {
421 // Still in use - in production you'd add to retirement queue
422 // For now, leak it (registrations are infrequent)
423 // TODO: Implement proper deferred reclamation queue
424 return;
425 }
426 }
427 delete list;
428}
429#endif
430
431} // namespace MayaFlux::Core
#define MF_INFO(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
Eigen::Index count
void register_node(const std::shared_ptr< Nodes::Input::InputNode > &node, InputBinding binding)
Register a node to receive input.
size_t get_queue_depth() const
Get current queue depth.
size_t get_registered_node_count() const
Get count of registered nodes.
std::vector< NodeRegistration > RegistrationList
std::atomic< std::shared_ptr< const RegistrationList > > m_registrations
std::atomic< bool > m_stop_requested
std::atomic< bool > m_queue_notify
Registry::Service::InputService * m_input_service
std::optional< InputBinding > resolve_vid_pid(const InputBinding &binding, const std::vector< InputDeviceInfo > &devices) const
void start()
Start the processing thread.
void unregister_node(const std::shared_ptr< Nodes::Input::InputNode > &node)
Unregister a node.
void enqueue(const InputValue &value)
Enqueue an input value for processing.
Memory::LockFreeQueue< InputValue, MAX_QUEUE_SIZE > m_queue
void dispatch_to_nodes(const InputValue &value)
void stop()
Stop the processing thread.
std::atomic< bool > m_running
std::atomic< uint64_t > m_events_processed
void enqueue_batch(const std::vector< InputValue > &values)
Enqueue multiple input values.
std::vector< std::shared_ptr< Nodes::Input::InputNode > > m_tracked_nodes
To keep nodes alive.
void unregister_all_nodes()
Unregister all nodes.
bool matches_binding(const InputValue &value, const InputBinding &binding) const
Interface * get_service()
Query for a backend service.
static BackendRegistry & instance()
Get the global registry instance.
@ OSC
Open Sound Control (network)
@ MIDI
MIDI controllers and instruments.
@ InputManagement
Input management (Core::InputManager)
@ Init
Engine/subsystem initialization.
@ AsyncIO
Async I/O operations ( network, streaming)
@ Core
Core engine, backend, subsystems.
std::optional< uint8_t > midi_cc_number
Match specific CC number.
InputType backend
Which backend type.
std::optional< std::string > osc_address_pattern
Match OSC address prefix.
std::optional< uint16_t > hid_vendor_id
Match HID vendor ID.
std::optional< uint8_t > midi_channel
Match specific MIDI channel (1-16)
std::optional< uint8_t > midi_message_type
Match message type (0xB0=CC, 0x90=NoteOn, etc.)
std::optional< uint16_t > hid_product_id
Match HID product ID.
uint32_t device_id
Specific device (0 = any device)
Specifies what input an InputNode wants to receive.
uint32_t device_id
Source device identifier.
const OSCMessage & as_osc() const
@ OSC
Structured OSC message.
@ MIDI
Structured MIDI message.
InputType source_type
Backend that generated this value.
const MIDIMessage & as_midi() const
Generic input value container.
std::function< std::vector< Core::InputDeviceInfo >()> get_all_devices
Query all available input devices across all backends.
std::function< bool(Core::InputType, uint32_t)> open_device
Open a specific input device.
Backend input device service interface.