use crate::{async_io::AsyncInstrument, Error, Instrument, JobID};
use std::{
io,
pin::Pin,
task::{Context, Poll},
};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
pub struct InstrumentTokioAdapter {
instr: AsyncInstrument,
read_current: Option<JobID>,
write_current: Option<JobID>,
read_buf: Vec<u8>,
read_pos: usize,
write_buf: Vec<u8>,
}
impl TryFrom<Instrument> for InstrumentTokioAdapter {
type Error = Error;
fn try_from(value: Instrument) -> Result<Self, Self::Error> {
Ok(Self::new(AsyncInstrument::new(value)?))
}
}
impl From<AsyncInstrument> for InstrumentTokioAdapter {
fn from(value: AsyncInstrument) -> Self {
Self::new(value)
}
}
impl From<InstrumentTokioAdapter> for AsyncInstrument {
fn from(mut value: InstrumentTokioAdapter) -> Self {
value.cancel_in_flight();
let value = std::mem::ManuallyDrop::new(value);
let instr = unsafe { std::ptr::read(&value.instr) };
drop(unsafe { std::ptr::read(&value.read_buf) });
drop(unsafe { std::ptr::read(&value.write_buf) });
instr
}
}
impl From<InstrumentTokioAdapter> for Instrument {
fn from(value: InstrumentTokioAdapter) -> Self {
let async_instr: AsyncInstrument = value.into();
async_instr.into()
}
}
impl InstrumentTokioAdapter {
pub fn new(instr: AsyncInstrument) -> Self {
Self {
instr,
read_current: None,
write_current: None,
read_buf: Vec::new(),
read_pos: 0,
write_buf: Vec::new(),
}
}
fn map_vs_err(err: Error) -> io::Error {
io::Error::other(err)
}
fn cancel_in_flight(&mut self) {
if let Some(job_id) = self.read_current.take() {
self.instr.cancel_job(job_id);
}
if let Some(job_id) = self.write_current.take() {
self.instr.cancel_job(job_id);
}
}
fn deliver(&mut self, buf: &mut ReadBuf<'_>) {
let available = &self.read_buf[self.read_pos..];
let n = available.len().min(buf.remaining());
buf.put_slice(&available[..n]);
self.read_pos += n;
if self.read_pos == self.read_buf.len() {
self.read_buf.clear();
self.read_pos = 0;
}
}
fn poll_current_write(&mut self, cx: &Context<'_>) -> Poll<io::Result<()>> {
let Some(job_id) = self.write_current else {
return Poll::Ready(Ok(()));
};
match self.instr.poll_job(job_id, cx) {
Poll::Ready((result, mut buf)) => {
self.write_current = None;
let expected = buf.len();
buf.clear();
self.write_buf = buf;
let completion = result.map_err(|e| {
log::error!("tokio async write completion error: {}", e);
Self::map_vs_err(e)
})?;
if completion.count != expected {
let kind = if completion.count == 0 {
io::ErrorKind::WriteZero
} else if completion.count > expected {
io::ErrorKind::InvalidData
} else {
io::ErrorKind::Other
};
return Poll::Ready(Err(io::Error::new(
kind,
format!(
"VISA wrote {} of {expected} accepted bytes",
completion.count
),
)));
}
Poll::Ready(Ok(()))
}
Poll::Pending => Poll::Pending,
}
}
}
impl Drop for InstrumentTokioAdapter {
fn drop(&mut self) {
self.cancel_in_flight();
}
}
impl AsyncRead for InstrumentTokioAdapter {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
let me = self.get_mut();
if buf.remaining() == 0 {
return Poll::Ready(Ok(()));
}
if me.read_current.is_none() {
if me.read_pos < me.read_buf.len() {
me.deliver(buf);
return Poll::Ready(Ok(()));
}
let mut owned = std::mem::take(&mut me.read_buf);
owned.clear();
owned.resize(
crate::instrument::async_transfer_count(buf.remaining()) as usize,
0,
);
me.read_pos = 0;
match me.instr.start_read_id(owned, cx.waker()) {
Ok(job_id) => me.read_current = Some(job_id),
Err(e) => return Poll::Ready(Err(Self::map_vs_err(e))),
}
}
let job_id = me.read_current.expect("set immediately above");
match me.instr.poll_job(job_id, cx) {
Poll::Ready((Ok(c), mut owned)) => {
me.read_current = None;
owned.truncate(c.count.min(owned.len()));
me.read_buf = owned;
me.read_pos = 0;
me.deliver(buf);
Poll::Ready(Ok(()))
}
Poll::Ready((Err(e), mut owned)) => {
me.read_current = None;
owned.clear();
me.read_buf = owned;
me.read_pos = 0;
log::error!("tokio async read completion error: {}", e);
Poll::Ready(Err(Self::map_vs_err(e)))
}
Poll::Pending => Poll::Pending,
}
}
}
impl AsyncWrite for InstrumentTokioAdapter {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
let me = self.get_mut();
match me.poll_current_write(cx) {
Poll::Pending => {
if !buf.is_empty() {
log::warn!(
"tokio write waiting for previous VISA job; {} input bytes not accepted yet",
buf.len()
);
}
return Poll::Pending;
}
Poll::Ready(result) => result?,
}
if buf.is_empty() {
return Poll::Ready(Ok(0));
}
let n = crate::instrument::async_transfer_count(buf.len()) as usize;
let mut owned = std::mem::take(&mut me.write_buf);
owned.clear();
owned.extend_from_slice(&buf[..n]);
match me.instr.start_write_id(owned, cx.waker()) {
Ok(job_id) => me.write_current = Some(job_id),
Err(e) => return Poll::Ready(Err(Self::map_vs_err(e))),
}
Poll::Ready(Ok(n))
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
use crate::flags::FlushMode;
let me = self.get_mut();
std::task::ready!(me.poll_current_write(cx))?;
me.instr
.instrument()
.visa_flush(FlushMode::WRITE_BUF | FlushMode::IO_OUT_BUF)
.map_err(Self::map_vs_err)?;
Poll::Ready(Ok(()))
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.poll_flush(cx)
}
}