Skip to main content

maolan_engine/workers/
hw_worker.rs

1use crate::{
2    hw::traits::{HwMidiHub, HwWorkerDriver},
3    message::{HwMidiEvent, Message},
4};
5#[cfg(unix)]
6use nix::libc;
7use std::sync::Arc;
8use std::sync::atomic::{AtomicBool, Ordering};
9use std::time::Duration;
10use tokio::sync::mpsc::{Receiver, Sender};
11use tracing::error;
12
13pub trait Backend: Send + Sync + 'static {
14    type Driver: HwWorkerDriver + Send + 'static;
15    type MidiHub: HwMidiHub + Send + 'static;
16
17    const LABEL: &'static str;
18    const WORKER_THREAD_NAME: &'static str;
19    const ASSIST_THREAD_NAME: &'static str;
20    const ASSIST_AUTONOMOUS_ENV: &'static str;
21    const ASSIST_AUTONOMOUS_DEFAULT: bool = false;
22    const CYCLE_ON_WORKER_WHEN_ASSIST_AUTONOMOUS: bool = false;
23    const ASSIST_STEP_REQUIRES_REQUEST_CYCLE: bool = false;
24}
25
26#[derive(Debug)]
27pub struct HwWorker<B: Backend> {
28    /// Owned by the worker between cycles; shuttled to the spawn_blocking
29    /// thread for the duration of each audio cycle (`None` only while a
30    /// cycle is in flight, during which the message channel is not polled).
31    driver: Option<B::Driver>,
32    midi_hub: B::MidiHub,
33    rx: Receiver<Message>,
34    tx: Sender<Message>,
35    cycle_frames: u32,
36    pending_midi_out_events: Vec<HwMidiEvent>,
37    pending_midi_out_sorted: bool,
38    midi_stop: Arc<AtomicBool>,
39    /// Mirrors the last `HWSetPlaying`; while stopped, MIDI-out events are
40    /// flushed on receipt (panic All-Sound-Off must not wait for a cycle)
41    /// and MIDI input is drained by a periodic timer instead of per cycle.
42    playing: bool,
43}
44
45/// How often hardware MIDI input is polled when no audio cycles are running
46/// (transport stopped). While playing, input is drained every cycle and this
47/// timer is only a harmless extra non-blocking read.
48const MIDI_INPUT_POLL_INTERVAL: Duration = Duration::from_millis(10);
49
50impl<B: Backend> Drop for HwWorker<B> {
51    fn drop(&mut self) {
52        if let Some(driver) = self.driver.as_mut() {
53            driver.request_stop();
54        }
55        self.midi_stop.store(true, Ordering::Release);
56        self.midi_hub.wake_input_waiter();
57        self.midi_hub.close_all();
58        if let Some(driver) = self.driver.as_mut() {
59            driver.close_fds();
60        }
61    }
62}
63
64#[cfg(unix)]
65const RT_POLICY: i32 = libc::SCHED_FIFO;
66const RT_PRIORITY_WORKER: i32 = 18;
67
68impl<B: Backend> HwWorker<B> {
69    fn configure_rt_thread(name: &str, priority: i32) -> Result<(), String> {
70        #[cfg(unix)]
71        {
72            let thread = unsafe { libc::pthread_self() };
73            #[cfg(unix)]
74            let c_name = std::ffi::CString::new(name).map_err(|e| e.to_string())?;
75            #[cfg(target_os = "linux")]
76            unsafe {
77                let _ = libc::pthread_setname_np(thread, c_name.as_ptr());
78            }
79            #[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
80            unsafe {
81                libc::pthread_set_name_np(thread, c_name.as_ptr());
82            }
83
84            let param = unsafe {
85                let mut p = std::mem::zeroed::<libc::sched_param>();
86                p.sched_priority = priority;
87                p
88            };
89            let rc = unsafe { libc::pthread_setschedparam(thread, RT_POLICY, &param) };
90            if rc != 0 {
91                return Err(format!(
92                    "pthread_setschedparam({}, prio {}) failed with errno {}",
93                    name, priority, rc
94                ));
95            }
96
97            let mut actual_policy = 0_i32;
98            let mut actual_param = unsafe { std::mem::zeroed::<libc::sched_param>() };
99            let rc = unsafe {
100                libc::pthread_getschedparam(thread, &mut actual_policy, &mut actual_param)
101            };
102            if rc != 0 {
103                return Err(format!(
104                    "pthread_getschedparam({}) failed with errno {}",
105                    name, rc
106                ));
107            }
108            if actual_policy != RT_POLICY || actual_param.sched_priority != priority {
109                return Err(format!(
110                    "realtime verification failed for {}: policy {}, prio {}",
111                    name, actual_policy, actual_param.sched_priority
112                ));
113            }
114            Ok(())
115        }
116        #[cfg(target_os = "windows")]
117        {
118            use std::{cell::Cell, ffi::OsStr, os::windows::ffi::OsStrExt};
119
120            #[link(name = "avrt")]
121            unsafe extern "system" {
122                fn AvSetMmThreadCharacteristicsW(
123                    task_name: *const u16,
124                    task_index: *mut u32,
125                ) -> isize;
126            }
127
128            let _ = priority;
129            thread_local! {
130                static MMCSS_TASK_HANDLE: Cell<isize> = const { Cell::new(0) };
131            }
132
133            MMCSS_TASK_HANDLE.with(|handle| {
134                if handle.get() != 0 {
135                    return Ok(());
136                }
137
138                let task_name: Vec<u16> = OsStr::new("Pro Audio")
139                    .encode_wide()
140                    .chain(Some(0))
141                    .collect();
142                let mut task_index = 0_u32;
143                let mmcss_handle =
144                    unsafe { AvSetMmThreadCharacteristicsW(task_name.as_ptr(), &mut task_index) };
145                if mmcss_handle == 0 {
146                    Err(format!(
147                        "AvSetMmThreadCharacteristicsW({name}, Pro Audio) failed: {}",
148                        std::io::Error::last_os_error()
149                    ))
150                } else {
151                    handle.set(mmcss_handle);
152                    Ok(())
153                }
154            })
155        }
156        #[cfg(all(not(unix), not(target_os = "windows")))]
157        {
158            let _ = name;
159            let _ = priority;
160            Err("Realtime thread priority is not supported on this platform".to_string())
161        }
162    }
163
164    #[cfg(unix)]
165    fn lock_memory_pages() -> Result<(), String> {
166        let rc = unsafe { libc::mlockall(libc::MCL_CURRENT | libc::MCL_FUTURE) };
167        if rc == 0 {
168            Ok(())
169        } else {
170            Err(format!(
171                "mlockall(MCL_CURRENT|MCL_FUTURE) failed: {}",
172                std::io::Error::last_os_error()
173            ))
174        }
175    }
176
177    pub fn new(
178        driver: B::Driver,
179        midi_hub: B::MidiHub,
180        rx: Receiver<Message>,
181        tx: Sender<Message>,
182    ) -> Self {
183        let cycle_frames = driver.cycle_samples() as u32;
184        Self {
185            driver: Some(driver),
186            midi_hub,
187            rx,
188            tx,
189            cycle_frames,
190            pending_midi_out_events: vec![],
191            pending_midi_out_sorted: true,
192            midi_stop: Arc::new(AtomicBool::new(false)),
193            playing: false,
194        }
195    }
196
197    fn driver_mut(&mut self) -> &mut B::Driver {
198        self.driver
199            .as_mut()
200            .expect("driver is only absent while a cycle runs on the blocking thread")
201    }
202
203    /// Run one audio cycle on a tokio blocking thread. The blocking pool
204    /// thread does not inherit the async worker thread's realtime priority,
205    /// so configure it for every cycle — the pool may hand each cycle to a
206    /// different thread.
207    fn run_cycle_blocking(mut driver: B::Driver) -> (B::Driver, Result<(), String>) {
208        let rt_start = std::time::Instant::now();
209        if let Err(e) = Self::configure_rt_thread(B::WORKER_THREAD_NAME, RT_PRIORITY_WORKER) {
210            static WARNED: std::sync::atomic::AtomicBool =
211                std::sync::atomic::AtomicBool::new(false);
212            if !WARNED.swap(true, std::sync::atomic::Ordering::Relaxed) {
213                tracing::warn!(
214                    "{} cycle thread realtime priority not enabled: {}",
215                    B::LABEL,
216                    e
217                );
218            }
219        }
220        let _rt_us = rt_start.elapsed().as_micros() as u64;
221        let _cycle_start = std::time::Instant::now();
222        let result = driver.run_cycle_for_worker();
223        let _cycle_us = _cycle_start.elapsed().as_micros() as u64;
224        (driver, result)
225    }
226
227    pub async fn work(mut self) {
228        crate::enable_flush_denormals_to_zero();
229        #[cfg(unix)]
230        {
231            if let Err(e) = Self::lock_memory_pages() {
232                error!("{} worker memory lock not enabled: {}", B::LABEL, e);
233            }
234        }
235        if let Err(e) = Self::configure_rt_thread(B::WORKER_THREAD_NAME, RT_PRIORITY_WORKER) {
236            error!("{} worker realtime priority not enabled: {}", B::LABEL, e);
237        }
238        #[cfg(unix)]
239        {
240            let has_fds = self
241                .driver
242                .as_ref()
243                .is_some_and(|d| d.capture_fd().is_some() && d.playback_fd().is_some());
244            if has_fds {
245                self.work_async().await;
246                return;
247            }
248        }
249
250        self.work_legacy().await;
251    }
252
253    #[cfg(unix)]
254    async fn work_async(&mut self) {
255        let mut cycle_running = false;
256        let (cycle_tx, mut cycle_rx) =
257            tokio::sync::mpsc::channel::<(B::Driver, Result<(), String>)>(1);
258        let mut midi_input_poll = tokio::time::interval(MIDI_INPUT_POLL_INTERVAL);
259        loop {
260            tokio::select! {
261                // While a cycle is in flight the driver lives on the blocking
262                // thread, so the message channel is not polled; queued
263                // messages (including Quit) are handled once the cycle
264                // returns — bounded by one audio period.
265                msg = self.rx.recv(), if !cycle_running => {
266                    let msg = match msg {
267                        Some(m) => m,
268                        None => {
269                            self.driver_mut().request_stop();
270                            self.shutdown_channel_closed();
271                            return;
272                        }
273                    };
274                    match msg {
275                        Message::Request(crate::message::Action::Quit) => {
276                            self.driver_mut().request_stop();
277                            self.shutdown_quit();
278                            return;
279                        }
280                        Message::TracksFinished => {
281                            self.flush_pending_midi_out();
282                            self.drain_midi_input().await;
283                            if !cycle_running {
284                                cycle_running = true;
285                                let tx = cycle_tx.clone();
286                                let driver = self.driver.take().expect(
287                                    "driver is only absent while a cycle is running",
288                                );
289                                tokio::task::spawn_blocking(move || {
290                                    let _ = tx.blocking_send(Self::run_cycle_blocking(driver));
291                                });
292                            }
293                        }
294                        Message::HWMidiOutEvents(mut events) => {
295                            self.pending_midi_out_events.append(&mut events);
296                            self.pending_midi_out_sorted = false;
297                            // Stopped transport means no cycles and no
298                            // TracksFinished to flush on; write immediately so
299                            // e.g. panic All-Sound-Off reaches the device.
300                            if !self.playing {
301                                self.flush_pending_midi_out();
302                            }
303                        }
304                        Message::ClearHWMidiOutEvents => {
305                            self.pending_midi_out_events.clear();
306                            self.pending_midi_out_sorted = true;
307                        }
308                        Message::HWSetPlaying(playing) => {
309                            self.playing = playing;
310                            self.driver_mut().set_playing(playing);
311                        }
312                        Message::HWSetOutputGainBalance { gain, balance } => {
313                            self.driver_mut().set_output_gain_balance(gain, balance);
314                        }
315                        Message::HWOpenMidiInputDevice(device) => {
316                            let result = self.midi_hub.open_input(&device);
317                            let action = crate::message::Action::OpenMidiInputDevice(device);
318                            let _ = self.tx.send(Message::Response(result.map(|_| action))).await;
319                        }
320                        Message::HWOpenMidiOutputDevice(device) => {
321                            let result = self.midi_hub.open_output(&device);
322                            let action = crate::message::Action::OpenMidiOutputDevice(device);
323                            let _ = self.tx.send(Message::Response(result.map(|_| action))).await;
324                        }
325                        Message::HWCloseMidiDevices => {
326                            self.midi_hub.close_all();
327                        }
328                        _ => {}
329                    }
330                }
331                result = cycle_rx.recv(), if cycle_running => {
332                    cycle_running = false;
333                    if let Some((driver, result)) = result {
334                        self.driver = Some(driver);
335                        if let Err(e) = result {
336                            error!("{} cycle error: {}", B::LABEL, e);
337                            let _ = self.tx.send(Message::Response(Err(format!(
338                                "{} cycle error: {}", B::LABEL, e
339                            )))).await;
340                        }
341                    }
342                    if let Err(e) = self.tx.send(Message::HWFinished).await {
343                        error!("{} worker failed to send HWFinished: {}", B::LABEL, e);
344                    }
345                }
346                // Hardware MIDI input must flow even while the transport is
347                // stopped (MIDI learn, monitoring, external controllers);
348                // while playing, input is drained every cycle on
349                // TracksFinished and this is an extra non-blocking read.
350                _ = midi_input_poll.tick(), if !cycle_running => {
351                    self.drain_midi_input().await;
352                }
353            }
354        }
355    }
356
357    async fn work_legacy(&mut self) {
358        let mut midi_input_poll = tokio::time::interval(MIDI_INPUT_POLL_INTERVAL);
359        loop {
360            let msg = tokio::select! {
361                msg = self.rx.recv() => match msg {
362                    Some(msg) => msg,
363                    None => {
364                        self.driver_mut().request_stop();
365                        self.shutdown_midi();
366                        self.driver_mut().close_fds();
367                        return;
368                    }
369                },
370                // Keep hardware MIDI input flowing while the transport is
371                // stopped; see work_async.
372                _ = midi_input_poll.tick() => {
373                    self.drain_midi_input().await;
374                    continue;
375                }
376            };
377            match msg {
378                Message::Request(crate::message::Action::Quit) => {
379                    self.driver_mut().request_stop();
380                    self.flush_pending_midi_out();
381                    self.shutdown_midi();
382                    self.driver_mut().close_fds();
383                    self.driver_mut().request_stop();
384                    return;
385                }
386                Message::TracksFinished => {
387                    self.flush_pending_midi_out();
388                    self.drain_midi_input().await;
389                    // The cycle blocks for a full audio period; run it on a
390                    // blocking thread with per-cycle RT priority instead of
391                    // stalling the async worker task (see work_async).
392                    let driver = self
393                        .driver
394                        .take()
395                        .expect("driver is only absent while a cycle is running");
396                    let cycle =
397                        tokio::task::spawn_blocking(move || Self::run_cycle_blocking(driver));
398                    match cycle.await {
399                        Ok((driver, result)) => {
400                            self.driver = Some(driver);
401                            if let Err(e) = result {
402                                error!("{} assist cycle error: {}", B::LABEL, e);
403                                let _ = self
404                                    .tx
405                                    .send(Message::Response(Err(format!(
406                                        "{} assist cycle error: {}",
407                                        B::LABEL,
408                                        e
409                                    ))))
410                                    .await;
411                            }
412                        }
413                        Err(e) => {
414                            error!("{} cycle task failed: {}", B::LABEL, e);
415                            return;
416                        }
417                    }
418                    if let Err(e) = self.tx.send(Message::HWFinished).await {
419                        error!(
420                            "{} worker failed to send HWFinished to engine: {}",
421                            B::LABEL,
422                            e
423                        );
424                    }
425                }
426                Message::HWMidiOutEvents(mut events) => {
427                    self.pending_midi_out_events.append(&mut events);
428                    self.pending_midi_out_sorted = false;
429                    // Stopped transport means no cycles and no TracksFinished
430                    // to flush on; write immediately (panic All-Sound-Off).
431                    if !self.playing {
432                        self.flush_pending_midi_out();
433                    }
434                }
435                Message::ClearHWMidiOutEvents => {
436                    self.pending_midi_out_events.clear();
437                    self.pending_midi_out_sorted = true;
438                }
439                Message::HWSetPlaying(playing) => {
440                    self.playing = playing;
441                    self.driver_mut().set_playing(playing);
442                }
443                Message::HWSetOutputGainBalance { gain, balance } => {
444                    self.driver_mut().set_output_gain_balance(gain, balance);
445                }
446                Message::HWOpenMidiInputDevice(device) => {
447                    let result = self.midi_hub.open_input(&device);
448                    let action = crate::message::Action::OpenMidiInputDevice(device);
449                    let _ = self
450                        .tx
451                        .send(Message::Response(result.map(|_| action)))
452                        .await;
453                }
454                Message::HWOpenMidiOutputDevice(device) => {
455                    let result = self.midi_hub.open_output(&device);
456                    let action = crate::message::Action::OpenMidiOutputDevice(device);
457                    let _ = self
458                        .tx
459                        .send(Message::Response(result.map(|_| action)))
460                        .await;
461                }
462                Message::HWCloseMidiDevices => {
463                    self.midi_hub.close_all();
464                }
465                _ => {}
466            }
467        }
468    }
469
470    fn flush_pending_midi_out(&mut self) {
471        if self.pending_midi_out_events.is_empty() {
472            return;
473        }
474        if !self.pending_midi_out_sorted {
475            self.pending_midi_out_events.sort_by(|a, b| {
476                a.event
477                    .frame
478                    .cmp(&b.event.frame)
479                    .then_with(|| a.device.cmp(&b.device))
480            });
481            self.pending_midi_out_sorted = true;
482        }
483        self.midi_hub.write_events(&self.pending_midi_out_events);
484        self.pending_midi_out_events.clear();
485    }
486
487    async fn drain_midi_input(&mut self) {
488        let mut midi_in_events = Vec::with_capacity(64);
489        self.midi_hub.read_events_into(&mut midi_in_events);
490        if midi_in_events.is_empty() {
491            return;
492        }
493        spread_hw_event_frames(&mut midi_in_events, self.cycle_frames);
494        let _ = self.tx.send(Message::HWMidiEvents(midi_in_events)).await;
495    }
496
497    fn shutdown_midi(&mut self) {
498        self.midi_stop.store(true, Ordering::Release);
499        self.midi_hub.wake_input_waiter();
500        self.midi_hub.close_all();
501    }
502
503    #[cfg(unix)]
504    fn shutdown_quit(&mut self) {
505        self.driver_mut().request_stop();
506        self.flush_pending_midi_out();
507        self.shutdown_midi();
508        self.driver_mut().close_fds();
509        self.driver_mut().request_stop();
510    }
511
512    #[cfg(unix)]
513    fn shutdown_channel_closed(&mut self) {
514        self.driver_mut().request_stop();
515        self.shutdown_midi();
516        self.driver_mut().close_fds();
517        self.driver_mut().request_stop();
518    }
519}
520
521fn spread_hw_event_frames(events: &mut [HwMidiEvent], frames: u32) {
522    if events.len() <= 1 || frames <= 1 {
523        return;
524    }
525    let n = events.len() as u32;
526    for (idx, event) in events.iter_mut().enumerate() {
527        let pos = idx as u32;
528        event.event.frame = ((pos as u64 * (frames - 1) as u64) / n as u64) as u32;
529    }
530}