WIP: not sure

This commit is contained in:
2026-02-03 02:31:55 +01:00
parent 33ee1822a5
commit b305df3d79
7 changed files with 547 additions and 159 deletions

165
src/engine/dispatcher.rs Normal file
View File

@@ -0,0 +1,165 @@
use arc_swap::ArcSwap;
use crossbeam_channel::{Receiver, RecvTimeoutError, Sender};
use std::cmp::Ordering;
use std::collections::BinaryHeap;
use std::sync::Arc;
use std::time::Duration;
use super::link::LinkState;
use super::sequencer::{AudioCommand, MidiCommand};
use super::timing::{SyncTime, ACTIVE_WAIT_THRESHOLD_US};
/// A command scheduled for dispatch at a specific time.
#[derive(Clone)]
pub struct TimedCommand {
pub command: DispatchCommand,
pub target_time_us: SyncTime,
}
/// Commands the dispatcher can send to audio/MIDI threads.
#[derive(Clone)]
pub enum DispatchCommand {
Audio { cmd: String, time: Option<f64> },
Midi(MidiCommand),
FlushMidi,
Hush,
Panic,
}
impl Ord for TimedCommand {
fn cmp(&self, other: &Self) -> Ordering {
// Reverse ordering for min-heap (earliest time first)
other.target_time_us.cmp(&self.target_time_us)
}
}
impl PartialOrd for TimedCommand {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for TimedCommand {
fn eq(&self, other: &Self) -> bool {
self.target_time_us == other.target_time_us
}
}
impl Eq for TimedCommand {}
/// Main dispatcher loop - receives timed commands and dispatches them at the right moment.
pub fn dispatcher_loop(
cmd_rx: Receiver<TimedCommand>,
audio_tx: Arc<ArcSwap<Sender<AudioCommand>>>,
midi_tx: Arc<ArcSwap<Sender<MidiCommand>>>,
link: Arc<LinkState>,
) {
let mut queue: BinaryHeap<TimedCommand> = BinaryHeap::with_capacity(256);
loop {
let current_us = link.clock_micros() as SyncTime;
// Calculate timeout based on next queued event
let timeout_us = queue
.peek()
.map(|cmd| cmd.target_time_us.saturating_sub(current_us))
.unwrap_or(100_000) // 100ms default when idle
.max(100); // Minimum 100μs to prevent busy-looping
// Receive new commands (with timeout)
match cmd_rx.recv_timeout(Duration::from_micros(timeout_us)) {
Ok(cmd) => queue.push(cmd),
Err(RecvTimeoutError::Timeout) => {}
Err(RecvTimeoutError::Disconnected) => break,
}
// Drain any additional pending commands
while let Ok(cmd) = cmd_rx.try_recv() {
queue.push(cmd);
}
// Dispatch ready commands
let current_us = link.clock_micros() as SyncTime;
while let Some(cmd) = queue.peek() {
if cmd.target_time_us <= current_us + ACTIVE_WAIT_THRESHOLD_US {
let cmd = queue.pop().unwrap();
wait_until_dispatch(cmd.target_time_us, &link);
dispatch_command(cmd.command, &audio_tx, &midi_tx);
} else {
break;
}
}
}
}
/// Active-wait until the target time for precise dispatch.
fn wait_until_dispatch(target_us: SyncTime, link: &LinkState) {
while (link.clock_micros() as SyncTime) < target_us {
std::hint::spin_loop();
}
}
/// Route a command to the appropriate output channel.
fn dispatch_command(
cmd: DispatchCommand,
audio_tx: &Arc<ArcSwap<Sender<AudioCommand>>>,
midi_tx: &Arc<ArcSwap<Sender<MidiCommand>>>,
) {
match cmd {
DispatchCommand::Audio { cmd, time } => {
let _ = audio_tx
.load()
.try_send(AudioCommand::Evaluate { cmd, time });
}
DispatchCommand::Midi(midi_cmd) => {
let _ = midi_tx.load().try_send(midi_cmd);
}
DispatchCommand::FlushMidi => {
// Send All Notes Off (CC 123) on all 16 channels for all 4 devices
for dev in 0..4u8 {
for chan in 0..16u8 {
let _ = midi_tx.load().try_send(MidiCommand::CC {
device: dev,
channel: chan,
cc: 123,
value: 0,
});
}
}
}
DispatchCommand::Hush => {
let _ = audio_tx.load().try_send(AudioCommand::Hush);
}
DispatchCommand::Panic => {
let _ = audio_tx.load().try_send(AudioCommand::Panic);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_timed_command_ordering() {
let mut heap: BinaryHeap<TimedCommand> = BinaryHeap::new();
heap.push(TimedCommand {
command: DispatchCommand::Hush,
target_time_us: 300,
});
heap.push(TimedCommand {
command: DispatchCommand::Hush,
target_time_us: 100,
});
heap.push(TimedCommand {
command: DispatchCommand::Hush,
target_time_us: 200,
});
// Min-heap: earliest time should come out first
assert_eq!(heap.pop().unwrap().target_time_us, 100);
assert_eq!(heap.pop().unwrap().target_time_us, 200);
assert_eq!(heap.pop().unwrap().target_time_us, 300);
}
}