use std::collections::{HashMap, VecDeque};
use crate::channel::Discriminant;
use crate::multiplexer::SonicConnectionTrait;
use crate::transport::Transport;
use crate::{COMMAND_QUEUE_SIZE, Command};
pub struct SonicConnection<T, Discriminant> {
stream: T,
parse_line: Box<dyn Fn(&str) -> std::io::Result<(Discriminant, &str)> + Send>,
task_rx: crossbeam_channel::Receiver<Task<Discriminant>>,
tasks: Tasks<Discriminant>,
}
impl<T, D> SonicConnection<T, D> {
pub fn new(
stream: T,
parse_line: impl Fn(&str) -> std::io::Result<(D, &str)> + Send + 'static,
) -> (Self, crossbeam_channel::Sender<Task<D>>) {
let (tx, rx) = crossbeam_channel::bounded(COMMAND_QUEUE_SIZE);
let this = Self {
stream,
parse_line: Box::new(parse_line),
task_rx: rx,
tasks: Tasks {
pending: HashMap::with_capacity(8),
},
};
(this, tx)
}
}
pub(crate) struct Task<Discriminant> {
pub command: Command,
pub discriminant: Discriminant,
pub callback: Box<dyn FnOnce(std::io::Result<(&str, &mut Tasks<Discriminant>)>) + Send>,
}
pub(crate) struct Tasks<Discriminant> {
pub(crate) pending:
HashMap<Discriminant, VecDeque<Box<dyn FnOnce(&str, &mut Tasks<Discriminant>) + Send>>>,
}
impl<D: Discriminant> Tasks<D> {
pub(crate) fn register_pending(
&mut self,
discriminant: D,
callback: Box<dyn FnOnce(&str, &mut Tasks<D>) + Send>,
) {
match self.pending.get_mut(&discriminant) {
Some(pending) => pending.push_back(callback),
None => {
let queue_capacity: usize = if discriminant.has_payload() {
1
} else {
COMMAND_QUEUE_SIZE
};
let mut pending = VecDeque::with_capacity(queue_capacity);
pending.push_back(callback);
self.pending.insert(discriminant, pending);
}
}
}
}
impl<T: Transport, D: Discriminant> SonicConnectionTrait for SonicConnection<T, D> {
#[inline]
fn wants_to_write(&self) -> bool {
!self.task_rx.is_empty()
}
#[inline]
fn wants_to_read(&self) -> bool {
!self.tasks.pending.is_empty()
}
fn interest(&self) -> Option<mio::Interest> {
match (self.wants_to_write(), self.wants_to_read()) {
(false, false) => None,
(true, false) => Some(mio::Interest::WRITABLE),
(false, true) => Some(mio::Interest::READABLE),
(true, true) => Some(mio::Interest::READABLE | mio::Interest::WRITABLE),
}
}
fn drain_reads(&mut self) -> std::io::Result<()> {
'process_lines: for line_bytes in self.stream.read_lines()? {
log_trace!("Read {} bytes line", line_bytes.len());
let (discriminant, data) = match str::from_utf8(&line_bytes[..]) {
Ok(line) => match (self.parse_line)(line) {
Ok(ok) => ok,
Err(error) => {
log_warn!("Invalid message received from the server: {error}");
continue 'process_lines;
}
},
Err(error) => {
log_warn!("Invalid UTF-8 sequence received from the server: {error}");
continue 'process_lines;
}
};
let Some(pending) = self.tasks.pending.get_mut(&discriminant) else {
log_warn!(
"Unexpected message received from the server: {discriminant:?} (expected: {:?})",
self.tasks.pending.keys()
);
continue 'process_lines;
};
let Some(respond) = pending.pop_front() else {
log_warn!(
"Unexpected message received from the server: {discriminant:?} (queue empty)"
);
continue 'process_lines;
};
if discriminant.has_payload() && pending.is_empty() {
self.tasks.pending.remove(&discriminant);
}
respond(data, &mut self.tasks);
}
Ok(())
}
fn flush_writes(&mut self) -> std::io::Result<usize> {
for task in self.task_rx.try_iter().take(self.task_rx.len()) {
self.stream.write_line(task.command);
self.tasks.register_pending(
task.discriminant,
Box::new(move |str, dispatcher| (task.callback)(Ok((str, dispatcher)))),
);
}
self.stream.flush_writes()
}
}
impl<T: Transport, D> AsMut<mio::net::TcpStream> for SonicConnection<T, D> {
fn as_mut(&mut self) -> &mut mio::net::TcpStream {
self.stream.as_mut()
}
}