iv/src/subengine/subengine_controller.rs
Steins7 64624dca20 Temporarily fixed tests
* reenabled SubengineController tests
* fixed WinitWindow drop function
- disabled all tests involving winit
2021-01-19 11:30:52 +01:00

141 lines
4.4 KiB
Rust

#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
use std::{
sync::mpsc::{Sender, Receiver},
thread::JoinHandle,
mem::ManuallyDrop,
ptr::read,
time::Duration,
};
use crate::subengine::Subengine;
//--SubengineController Implementation--------------------------------------------------------------
/// Handle to manage an [`Subengine`].
///
/// The `SubengineController` runs the [`Engine`] in a separated thread with some control code to
/// allow control from the main thread. SubengineControllers are used by the [`Controller`] to
/// interact with Subengines.
#[derive(Debug)]
pub struct SubengineController {
tx: Sender<SubengineCommand>,
rx: Receiver<SubengineResponse>,
handle: ManuallyDrop<JoinHandle<()>>,
}
impl Drop for SubengineController {
fn drop(&mut self) {
self.exec(SubengineCommand::Stop);
unsafe { let _ = ManuallyDrop::into_inner(read(&mut self.handle)).join(); }
debug!("SubengineController dropped !");
}
}
impl SubengineController {
/// Creates a new `SubengineController` from a given [`Engine`]. Since the latter will be moved
/// to a separated thread, it must implement the [`Send`] trait.
pub fn new<E: 'static>(engine: E) -> SubengineController
where
E: Subengine + std::marker::Send,
{
use std::{
thread,
sync::mpsc::channel,
};
debug!("Creating SubengineController...");
let (tx, e_rx) = channel();
let (e_tx, rx) = channel();
let handle = thread::spawn(move || {
trace!("Subengine thread started !");
loop {
//TODO manage errors
match e_rx.recv().unwrap() {
SubengineCommand::Run => engine.run(),
SubengineCommand::Stop => return,
};
e_tx.send(SubengineResponse::Done).unwrap();
}
});
SubengineController {
tx,
rx,
handle: ManuallyDrop::new(handle),
}
}
/// Sends a command to the [`Subengine`]. This is function is not blocking and WILL NOT check if
/// the command was received.
pub fn exec(&self, cmd: SubengineCommand) {
self.tx.send(cmd).unwrap();
}
/// Blocking function, waits for the [`Subengine`] to finish executing the last command given.
pub fn wait_for_exec(&self, timeout: Duration) -> Result<(),SubengineWaitError> {
match self.rx.recv_timeout(timeout) {
Err(_) => Err(SubengineWaitError::NoResponse),
Ok(val) => match val {
SubengineResponse::Done => Ok(()),
//_ => Err(SubengineWaitError::BadResponse),
}}
}
}
//--SubengineCommand enum---------------------------------------------------------------------------
/// Commands that can be sent to an [`Subengine`] via an `EngineController`.
#[derive(Debug)]
pub enum SubengineCommand {
Run,
Stop,
}
//--SubengineWaitError enum-------------------------------------------------------------------------
/// Errors that can be returned by the wait_for_exec function.
#[derive(Debug)]
pub enum SubengineWaitError {
NoResponse,
BadResponse,
}
//--SubengineResponse enum--------------------------------------------------------------------------
#[derive(Debug)]
enum SubengineResponse {
Done,
}
//--Tests-------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
use crate::subengine::TestSubengine;
#[test]
fn test_new_drop() {
let (test_subengine, _test_rx) = TestSubengine::new("run");
let _subengine_controller = SubengineController::new(test_subengine);
}
#[test]
fn test_exec() {
let (test_subengine, test_rx) = TestSubengine::new("run");
let subengine_controller = SubengineController::new(test_subengine);
subengine_controller.exec(SubengineCommand::Run);
let response = test_rx.recv_timeout(Duration::from_millis(10)).unwrap();
assert_eq!(response, "run");
}
#[test]
fn test_wait_for_exec() {
let (test_subengine, _test_rx) = TestSubengine::new("run");
let subengine_controller = SubengineController::new(test_subengine);
subengine_controller.exec(SubengineCommand::Run);
subengine_controller.wait_for_exec(Duration::from_millis(10)).unwrap();
}
}