Module mmdvm_adapter

Module mmdvm_adapter 

Source
Expand description

Adapter bridging crate::Transport to tokio’s [AsyncRead] + [AsyncWrite] contracts.

The mmdvm crate’s tokio shell requires transports that implement [tokio::io::AsyncRead] and [tokio::io::AsyncWrite]. The crate::Transport trait exposes ergonomic async fn read / async fn write methods, which are incompatible at the trait-object level. This adapter converts the async fn interface into the poll-based interface tokio uses internally.

§Implementation strategy

A pump task owns the inner transport and serializes reads and writes via [tokio::select!]. The adapter communicates with the pump via two mpsc channels:

  • Write channel (adapter → pump): byte buffers to write.
  • Read channel (pump → adapter): byte buffers read from the transport, one Vec<u8> per crate::Transport::read call.

The pump task’s select! interleaves read and write operations on the same T, so a pending read never blocks an outgoing write (and vice versa). This mirrors the serialization that [tokio::io::split] provides for types that support an explicit half-split, without requiring T to support one.

MmdvmTransportAdapter::into_inner closes the write channel, awaits the pump task’s [JoinHandle], and recovers the inner T that the pump returned on clean exit.

§Thread-affinity (macOS Bluetooth)

The pump task is spawned with [tokio::task::spawn_local] so it runs on the same OS thread as the calling [tokio::task::LocalSet]. This is required for crate::transport::BluetoothTransport on macOS: IOBluetooth’s RFCOMM channel callbacks are dispatched to the CFRunLoop of the thread that opened the channel (typically the main thread, before the tokio runtime starts). Pumping that runloop from a worker thread is a no-op — the callbacks never deliver data into the pipe that BluetoothTransport::read waits on. By keeping the pump on the same thread, every bt_pump_runloop() call drains pending callbacks where they actually live.

Callers must therefore construct this adapter from inside a [tokio::task::LocalSet]. For the REPL/TUI, the top-level run_repl future is launched via LocalSet::block_on, satisfying this requirement transparently.

Structs§

MmdvmTransportAdapter
Adapter that presents a crate::Transport as a tokio [AsyncRead] + [AsyncWrite] + Send + Unpin duplex stream.