]> code.octet-stream.net Git - m17rt/blobdiff - m17app/src/soundmodem.rs
Factor out output buffering for KISS sockets
[m17rt] / m17app / src / soundmodem.rs
index e7c0022643e52047c703943e9d1597eea787cb79..c0cbfbb3121480b2fa0ebf3264f2c0df757e2572 100644 (file)
@@ -1,11 +1,13 @@
 use crate::error::{M17Error, SoundmodemError};
 use crate::tnc::{Tnc, TncError};
+use crate::util::out_buffer::OutBuffer;
 use m17core::kiss::MAX_FRAME_LEN;
 use m17core::modem::{Demodulator, Modulator, ModulatorAction, SoftDemodulator, SoftModulator};
 use m17core::tnc::SoftTnc;
 use std::collections::VecDeque;
+use std::fmt::Display;
 use std::fs::File;
-use std::io::{self, ErrorKind, Read, Write};
+use std::io::{self, Read, Write};
 use std::path::PathBuf;
 use std::sync::mpsc::{channel, sync_channel, Receiver, Sender, SyncSender, TryRecvError};
 use std::sync::RwLock;
@@ -15,9 +17,7 @@ use thiserror::Error;
 
 pub struct Soundmodem {
     event_tx: SyncSender<SoundmodemEvent>,
-    kiss_out_rx: Arc<Mutex<Receiver<Arc<[u8]>>>>,
-    partial_kiss_out: Arc<Mutex<Option<PartialKissOut>>>,
-    error_handler: ErrorHandlerInternal,
+    kiss_out: OutBuffer,
 }
 
 impl Soundmodem {
@@ -29,7 +29,6 @@ impl Soundmodem {
     ) -> Self {
         let (event_tx, event_rx) = sync_channel(128);
         let (kiss_out_tx, kiss_out_rx) = sync_channel(128);
-        let runtime_error_handler: ErrorHandlerInternal = Arc::new(Mutex::new(Box::new(error)));
         spawn_soundmodem_worker(
             event_tx.clone(),
             event_rx,
@@ -37,13 +36,11 @@ impl Soundmodem {
             Box::new(input),
             Box::new(output),
             Box::new(ptt),
-            runtime_error_handler.clone(),
+            Box::new(error),
         );
         Self {
             event_tx,
-            kiss_out_rx: Arc::new(Mutex::new(kiss_out_rx)),
-            partial_kiss_out: Arc::new(Mutex::new(None)),
-            error_handler: runtime_error_handler,
+            kiss_out: OutBuffer::new(kiss_out_rx),
         }
     }
 }
@@ -55,6 +52,16 @@ pub enum ErrorSource {
     Ptt,
 }
 
+impl Display for ErrorSource {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::Input => write!(f, "Input"),
+            Self::Output => write!(f, "Output"),
+            Self::Ptt => write!(f, "PTT"),
+        }
+    }
+}
+
 pub trait ErrorHandler: Send + Sync + 'static {
     fn soundmodem_error(&mut self, source: ErrorSource, err: SoundmodemError);
 }
@@ -68,6 +75,7 @@ where
     }
 }
 
+/// Soundmodem errors will be ignored.
 pub struct NullErrorHandler;
 
 impl NullErrorHandler {
@@ -89,7 +97,47 @@ impl ErrorHandler for NullErrorHandler {
     }
 }
 
-type ErrorHandlerInternal = Arc<Mutex<Box<dyn ErrorHandler>>>;
+/// Soundmodem errors will be logged at DEBUG level via the `log` crate.
+pub struct LogErrorHandler;
+
+impl LogErrorHandler {
+    pub fn new() -> Self {
+        Self {}
+    }
+}
+
+impl Default for LogErrorHandler {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl ErrorHandler for LogErrorHandler {
+    fn soundmodem_error(&mut self, source: ErrorSource, err: SoundmodemError) {
+        log::debug!("Soundmodem error: {source} - {err}");
+    }
+}
+
+/// Soundmodem errors will be logged to stdout.
+pub struct StdoutErrorHandler;
+
+impl StdoutErrorHandler {
+    pub fn new() -> Self {
+        Self {}
+    }
+}
+
+impl Default for StdoutErrorHandler {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl ErrorHandler for StdoutErrorHandler {
+    fn soundmodem_error(&mut self, source: ErrorSource, err: SoundmodemError) {
+        println!("Soundmodem error: {source} - {err}");
+    }
+}
 
 #[derive(Clone)]
 pub struct SoundmodemErrorSender {
@@ -105,42 +153,9 @@ impl SoundmodemErrorSender {
     }
 }
 
-struct PartialKissOut {
-    output: Arc<[u8]>,
-    idx: usize,
-}
-
 impl Read for Soundmodem {
     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
-        {
-            let mut partial_kiss_out = self.partial_kiss_out.lock().unwrap();
-            if let Some(partial) = partial_kiss_out.as_mut() {
-                let remaining = partial.output.len() - partial.idx;
-                let to_write = remaining.min(buf.len());
-                buf[0..to_write]
-                    .copy_from_slice(&partial.output[partial.idx..(partial.idx + to_write)]);
-                if to_write == remaining {
-                    *partial_kiss_out = None;
-                } else {
-                    partial.idx += to_write;
-                }
-                return Ok(to_write);
-            }
-        }
-        let output = {
-            let rx = self.kiss_out_rx.lock().unwrap();
-            rx.recv()
-                .map_err(|s| io::Error::new(ErrorKind::Other, format!("{:?}", s)))?
-        };
-        let to_write = output.len().min(buf.len());
-        buf[0..to_write].copy_from_slice(&output[0..to_write]);
-        if to_write != output.len() {
-            *self.partial_kiss_out.lock().unwrap() = Some(PartialKissOut {
-                output,
-                idx: to_write,
-            })
-        }
-        Ok(to_write)
+        self.kiss_out.read(buf)
     }
 }
 
@@ -159,9 +174,7 @@ impl Tnc for Soundmodem {
     fn try_clone(&mut self) -> Result<Self, TncError> {
         Ok(Self {
             event_tx: self.event_tx.clone(),
-            kiss_out_rx: self.kiss_out_rx.clone(),
-            partial_kiss_out: self.partial_kiss_out.clone(),
-            error_handler: self.error_handler.clone(),
+            kiss_out: self.kiss_out.clone(),
         })
     }
 
@@ -191,7 +204,7 @@ fn spawn_soundmodem_worker(
     input: Box<dyn InputSource>,
     output: Box<dyn OutputSink>,
     mut ptt_driver: Box<dyn Ptt>,
-    error_handler: ErrorHandlerInternal,
+    mut error_handler: Box<dyn ErrorHandler>,
 ) {
     std::thread::spawn(move || {
         // TODO: should be able to provide a custom Demodulator for a soundmodem
@@ -251,10 +264,7 @@ fn spawn_soundmodem_worker(
                     input.close();
                     output.close();
                     if let Err(e) = ptt_driver.ptt_off() {
-                        error_handler
-                            .lock()
-                            .unwrap()
-                            .soundmodem_error(ErrorSource::Ptt, e);
+                        error_handler.soundmodem_error(ErrorSource::Ptt, e);
                     }
                     break;
                 }
@@ -277,7 +287,7 @@ fn spawn_soundmodem_worker(
                     // TODO: cancel transmission, send empty data frame to host
                 }
                 SoundmodemEvent::RuntimeError(source, err) => {
-                    error_handler.lock().unwrap().soundmodem_error(source, err);
+                    error_handler.soundmodem_error(source, err);
                 }
             }
 
@@ -286,16 +296,10 @@ fn spawn_soundmodem_worker(
             if new_ptt != ptt {
                 if new_ptt {
                     if let Err(e) = ptt_driver.ptt_on() {
-                        error_handler
-                            .lock()
-                            .unwrap()
-                            .soundmodem_error(ErrorSource::Ptt, e);
+                        error_handler.soundmodem_error(ErrorSource::Ptt, e);
                     }
                 } else if let Err(e) = ptt_driver.ptt_off() {
-                    error_handler
-                        .lock()
-                        .unwrap()
-                        .soundmodem_error(ErrorSource::Ptt, e);
+                    error_handler.soundmodem_error(ErrorSource::Ptt, e);
                 }
             }
             ptt = new_ptt;