use std::error::Error;
use std::fmt;
use std::io::{ErrorKind, Result as IoResult};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{
RecvError as MpscRecvError, SendError as MpscSendError, Sender,
TryRecvError as MpscTryRecvError, channel,
};
use std::thread;
use mio::event::Source;
use mio::{Events, Poll, Registry, Token, Waker};
use nexosim::model::{Model, SchedulableId};
use nexosim::simulation::ModelInjector;
use thread_guard::ThreadGuard;
pub trait IoPort<S, R, T>
where
S: Source + ?Sized,
R: Send,
T: Send,
{
fn register(&mut self, registry: &Registry) -> Token;
fn read(&mut self, token: Token) -> IoResult<R>;
fn write(&mut self, data: &T) -> IoResult<()>;
}
#[derive(Debug)]
pub enum SendError {
Disonnected,
IoError(std::io::Error),
}
impl<T> From<MpscSendError<T>> for SendError {
fn from(_: MpscSendError<T>) -> Self {
Self::Disonnected
}
}
impl From<std::io::Error> for SendError {
fn from(error: std::io::Error) -> Self {
Self::IoError(error)
}
}
impl fmt::Display for SendError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Disonnected => write!(f, "sending on a closed channel"),
Self::IoError(error) => error.fmt(f),
}
}
}
impl Error for SendError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Disonnected => None,
Self::IoError(error) => Some(error),
}
}
}
#[derive(Debug)]
pub enum TryRecvError {
Empty,
Disconnected,
}
impl From<MpscTryRecvError> for TryRecvError {
fn from(error: MpscTryRecvError) -> Self {
match error {
MpscTryRecvError::Empty => Self::Empty,
MpscTryRecvError::Disconnected => Self::Disconnected,
}
}
}
impl fmt::Display for TryRecvError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Empty => write!(f, "receiving on an empty channel"),
Self::Disconnected => write!(f, "receiving on a closed channel"),
}
}
}
impl Error for TryRecvError {}
#[derive(Debug)]
pub struct RecvError {}
impl From<MpscRecvError> for RecvError {
fn from(_: MpscRecvError) -> Self {
Self {}
}
}
impl fmt::Display for RecvError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Recv error")
}
}
impl Error for RecvError {}
pub struct IoThread<T>
where
T: Send,
{
_io_thread: ThreadGuard<()>,
transmitter: Sender<T>,
waker: Arc<Waker>,
}
impl<T> IoThread<T>
where
T: Send + 'static,
{
pub fn new<S, P, R, M>(
mut port: P,
injector: ModelInjector<M>,
schedulable: SchedulableId<M, R>,
) -> Self
where
S: Source + ?Sized,
P: IoPort<S, R, T> + Send + 'static,
R: Clone + Send + 'static,
M: Model,
{
let (transmitter, rx) = channel();
let is_halted = Arc::new(AtomicBool::new(false));
let guard_is_halted = is_halted.clone();
let mut poll = Poll::new().unwrap();
let wake = port.register(poll.registry());
let waker = Arc::new(Waker::new(poll.registry(), wake).unwrap());
let guard_waker = waker.clone();
let io_thread = thread::spawn(move || {
let mut events = Events::with_capacity(256);
'poll: loop {
poll.poll(&mut events, None).unwrap();
for event in events.iter() {
let token = event.token();
if token == wake {
if is_halted.load(Ordering::Relaxed) {
break 'poll;
}
while let Ok(data) = rx.try_recv() {
if port.write(&data).is_err() {
break 'poll;
}
}
} else {
loop {
match port.read(token) {
Ok(message) => injector.inject_event(&schedulable, message),
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
break;
}
_ => break 'poll,
}
}
}
}
}
});
Self {
_io_thread: ThreadGuard::with_pre_action(io_thread, move |_| {
guard_is_halted.store(true, Ordering::Relaxed);
let _ = guard_waker.wake();
guard_waker
}),
transmitter,
waker,
}
}
pub fn send(&mut self, data: T) -> Result<(), SendError> {
self.transmitter.send(data)?;
self.waker.wake()?;
Ok(())
}
}
impl<T> fmt::Debug for IoThread<T>
where
T: Send,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("IoThread").finish_non_exhaustive()
}
}