Add logging, graceful shutdown, webserver with websockets
This commit is contained in:
parent
50cf832c3f
commit
fdf781ff02
File diff suppressed because it is too large
Load Diff
|
@ -6,3 +6,11 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rand = "0.6.5"
|
rand = "0.6.5"
|
||||||
|
typed-html = "0.1.1"
|
||||||
|
warp = "0.1.13"
|
||||||
|
futures = "0.1.25"
|
||||||
|
log = "0.4.6"
|
||||||
|
simplelog = "0.5.3"
|
||||||
|
crossbeam-channel = "0.3.8"
|
||||||
|
ctrlc = "3.1.1"
|
||||||
|
tokio = "0.1.15"
|
|
@ -1,45 +1,56 @@
|
||||||
use crate::{TallyState, Channel};
|
use crate::common::Waitable;
|
||||||
use std::sync::mpsc;
|
use crate::{Channel, TallyState};
|
||||||
use std::sync::mpsc::{Sender, Receiver};
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
|
use std::thread;
|
||||||
|
use std::thread::JoinHandle;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
/// An Adaptor interfaces to a system providing tally information, e.g. a BlackMagic ATEM
|
/// An Adaptor interfaces to a system providing tally information, e.g. a BlackMagic ATEM
|
||||||
pub trait Adaptor {
|
pub trait Adaptor: Waitable {
|
||||||
/// Run the adaptor, returning a channel receiver which receives TallyStates
|
/// Run the adaptor, returning a channel receiver which receives TallyStates
|
||||||
fn run(&mut self) -> Receiver<TallyState>;
|
fn run(&mut self) -> Receiver<TallyState>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FakeAdaptor {
|
pub struct FakeAdaptor {
|
||||||
numchannels: u8,
|
numchannels: u8,
|
||||||
thread: Option<thread::JoinHandle<()>>
|
thread: Option<thread::JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FakeAdaptor {
|
impl FakeAdaptor {
|
||||||
pub fn new(numchannels: u8) -> FakeAdaptor {
|
pub fn new(numchannels: u8) -> FakeAdaptor {
|
||||||
return FakeAdaptor{
|
return FakeAdaptor {
|
||||||
numchannels: numchannels,
|
numchannels: numchannels,
|
||||||
thread: None
|
thread: None,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _run(numchannels: u8, sender: Sender<TallyState>) {
|
fn _run(numchannels: u8, sender: Sender<TallyState>) {
|
||||||
println!("Hello, I have {} channels", numchannels);
|
println!("Hello, I have {} channels", numchannels);
|
||||||
loop {
|
loop {
|
||||||
let state = TallyState{
|
let state = TallyState {
|
||||||
on_air: Channel(Some(rand::thread_rng().gen_range(0, numchannels))),
|
on_air: Channel(Some(rand::thread_rng().gen_range(0, numchannels))),
|
||||||
preview: Channel(Some(rand::thread_rng().gen_range(0, numchannels))),
|
preview: Channel(Some(rand::thread_rng().gen_range(0, numchannels))),
|
||||||
};
|
};
|
||||||
sender.send(state).unwrap();
|
if let Err(_) = sender.send(state) {
|
||||||
thread::sleep(Duration::new(rand::thread_rng().gen_range(0,5), 0));
|
log::info!("Fakeadaptor shutting down!");
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
thread::sleep(Duration::new(rand::thread_rng().gen_range(0, 5), 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Waitable for FakeAdaptor {
|
||||||
|
fn get_joinhandle(self) -> Option<JoinHandle<()>> {
|
||||||
|
self.thread
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Adaptor for FakeAdaptor {
|
impl Adaptor for FakeAdaptor {
|
||||||
fn run(&mut self) -> Receiver<TallyState> {
|
fn run(&mut self) -> Receiver<TallyState> {
|
||||||
println!("arse");
|
println!("arse");
|
||||||
let (tx, rx): (Sender<TallyState>, Receiver<TallyState>) = mpsc::channel();
|
let (tx, rx) = crossbeam_channel::unbounded::<TallyState>();
|
||||||
let thread_numchannels = self.numchannels.clone();
|
let thread_numchannels = self.numchannels.clone();
|
||||||
self.thread = Some(thread::spawn(move || {
|
self.thread = Some(thread::spawn(move || {
|
||||||
FakeAdaptor::_run(thread_numchannels, tx);
|
FakeAdaptor::_run(thread_numchannels, tx);
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
pub trait Waitable: Sized {
|
||||||
|
fn get_joinhandle(self) -> Option<thread::JoinHandle<()>>;
|
||||||
|
|
||||||
|
fn wait(self) {
|
||||||
|
if let Some(handle) = self.get_joinhandle() {
|
||||||
|
handle.join().expect("Couldn't join on thread!");
|
||||||
|
} else {
|
||||||
|
log::warn!("Tried to wait on non-existent thread!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +1,27 @@
|
||||||
|
use crate::common::Waitable;
|
||||||
use crate::TallyState;
|
use crate::TallyState;
|
||||||
|
|
||||||
use std::sync::mpsc;
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
use std::sync::mpsc::{Sender, Receiver};
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
/// A Driver displays the Tallies on a physical tally system.
|
/// A Driver displays the Tallies on a physical tally system.
|
||||||
pub trait Driver {
|
pub trait Driver: Waitable {
|
||||||
/// Run the driver, returning a channel sender which takes TallyStates
|
/// Run the driver, returning a channel sender which takes TallyStates
|
||||||
fn run(&mut self) -> Sender<TallyState>;
|
fn run(&mut self) -> Sender<TallyState>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FakeDriver {
|
pub struct FakeDriver {
|
||||||
thread: Option<thread::JoinHandle<()>>
|
thread: Option<thread::JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Waitable for FakeDriver {
|
||||||
|
fn get_joinhandle(self) -> Option<thread::JoinHandle<()>> {
|
||||||
|
self.thread
|
||||||
|
}
|
||||||
|
}
|
||||||
impl Driver for FakeDriver {
|
impl Driver for FakeDriver {
|
||||||
fn run(&mut self) -> Sender<TallyState> {
|
fn run(&mut self) -> Sender<TallyState> {
|
||||||
let (tx, rx): (Sender<TallyState>, Receiver<TallyState>) = mpsc::channel();
|
let (tx, rx) = crossbeam_channel::bounded::<TallyState>(1);
|
||||||
self.thread = Some(thread::spawn(move || {
|
self.thread = Some(thread::spawn(move || {
|
||||||
FakeDriver::_run(rx);
|
FakeDriver::_run(rx);
|
||||||
}));
|
}));
|
||||||
|
@ -26,15 +31,18 @@ impl Driver for FakeDriver {
|
||||||
|
|
||||||
impl FakeDriver {
|
impl FakeDriver {
|
||||||
pub fn new() -> FakeDriver {
|
pub fn new() -> FakeDriver {
|
||||||
FakeDriver{
|
FakeDriver { thread: None }
|
||||||
thread: None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _run(rx: Receiver<TallyState>) {
|
fn _run(rx: Receiver<TallyState>) {
|
||||||
loop {
|
loop {
|
||||||
let state = rx.recv().unwrap();
|
if let Ok(state) = rx.recv() {
|
||||||
println!("{}", state);
|
println!("{}", state);
|
||||||
|
} else {
|
||||||
|
// channel has been disconnected, shutdown.
|
||||||
|
log::info!("Fakedriver shutting down...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
44
src/main.rs
44
src/main.rs
|
@ -1,8 +1,13 @@
|
||||||
mod adaptors;
|
mod adaptors;
|
||||||
|
mod common;
|
||||||
mod drivers;
|
mod drivers;
|
||||||
|
mod web;
|
||||||
use crate::adaptors::{Adaptor, FakeAdaptor};
|
use crate::adaptors::{Adaptor, FakeAdaptor};
|
||||||
use crate::drivers::{Driver, FakeDriver};
|
use crate::drivers::{Driver, FakeDriver};
|
||||||
|
use crossbeam_channel::{select, Receiver};
|
||||||
|
use simplelog::{Config, LevelFilter, TermLogger};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use web::WebServer;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Channel(Option<u8>);
|
pub struct Channel(Option<u8>);
|
||||||
|
@ -34,20 +39,51 @@ struct Server<T: Adaptor, U: Driver> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Adaptor, U: Driver> Server<T, U> {
|
impl<T: Adaptor, U: Driver> Server<T, U> {
|
||||||
fn run(&mut self) {
|
fn run(mut self, signal_rx: Receiver<()>) {
|
||||||
let rx = self.adaptor.run();
|
let rx = self.adaptor.run();
|
||||||
let tx = self.driver.run();
|
let tx = self.driver.run();
|
||||||
|
let webaddr = ([127, 0, 0, 1], 3000).into();
|
||||||
|
log::info!("Webserver starting on: {}", webaddr);
|
||||||
|
let web = WebServer::run(webaddr);
|
||||||
loop {
|
loop {
|
||||||
tx.send(rx.recv().unwrap()).unwrap();
|
select! {
|
||||||
|
recv(signal_rx) -> _ => {
|
||||||
|
log::info!("Signal received, quitting!");
|
||||||
|
// signal webserver to shutdown and wait for exit..
|
||||||
|
web.shutdown();
|
||||||
|
// Drop the driver channel, and wait for it to shutdown...
|
||||||
|
drop(tx);
|
||||||
|
self.driver.wait();
|
||||||
|
|
||||||
|
// ...and do the same for the adaptor.
|
||||||
|
drop(rx);
|
||||||
|
self.adaptor.wait();
|
||||||
|
|
||||||
|
// We're done, break loop and return.
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
recv(rx) -> state => {
|
||||||
|
tx.send(state.unwrap()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut s = Server {
|
TermLogger::init(LevelFilter::Debug, Config::default()).unwrap();
|
||||||
|
|
||||||
|
let (signal_tx, signal_rx) = crossbeam_channel::bounded::<()>(1);
|
||||||
|
ctrlc::set_handler(move || {
|
||||||
|
log::info!("Received interrupt. Shutting down...");
|
||||||
|
signal_tx.send(()).expect("Error sending to signal channel");
|
||||||
|
})
|
||||||
|
.expect("Error setting signal handler");
|
||||||
|
|
||||||
|
let s = Server {
|
||||||
adaptor: FakeAdaptor::new(8),
|
adaptor: FakeAdaptor::new(8),
|
||||||
driver: FakeDriver::new(),
|
driver: FakeDriver::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
s.run();
|
s.run(signal_rx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
use futures::stream;
|
||||||
|
use futures::sync::oneshot::{channel, Sender};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
use typed_html::dom::DOMTree;
|
||||||
|
use typed_html::html;
|
||||||
|
use warp::filters::ws::Message;
|
||||||
|
use warp::{Filter, Future, Stream};
|
||||||
|
|
||||||
|
fn build_template() -> DOMTree<String> {
|
||||||
|
html!(
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>"OpenTally Control"</title>
|
||||||
|
<script src="/script.js" type="text/javascript"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>"OpenTally Control"</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WebServer {
|
||||||
|
shutdown_chan: Sender<()>,
|
||||||
|
shutdown_mutex: Arc<Mutex<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WebServer {
|
||||||
|
pub fn run(addr: std::net::SocketAddr) -> WebServer {
|
||||||
|
// todo: not have the damn js inline in the func
|
||||||
|
const JS: &'static str = r#"
|
||||||
|
var sock = new WebSocket("ws://" + window.location.host + "/ws");
|
||||||
|
sock.onmessage = function (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
sock.onopen = function (e) {
|
||||||
|
sock.send("Hello!");
|
||||||
|
}"#;
|
||||||
|
let (shutdown_tx, shutdown_rx) = channel::<()>();
|
||||||
|
|
||||||
|
let shutdown_mutex = Arc::new(Mutex::new(()));
|
||||||
|
let thread_shutdown_mutex = Arc::clone(&shutdown_mutex);
|
||||||
|
thread::spawn(move || {
|
||||||
|
// N.B! Despite the fact we never use the value inside the mutex (it's () after all..) we need to bind
|
||||||
|
// it to a variable so that it lives until the end of the thread, else it would unlock immediately.
|
||||||
|
let _lock = thread_shutdown_mutex.lock().unwrap();
|
||||||
|
|
||||||
|
let websocket = warp::path("ws").and(warp::ws2()).map(|ws: warp::ws::Ws2| {
|
||||||
|
ws.on_upgrade(|websocket| {
|
||||||
|
let (tx, rx) = websocket.split();
|
||||||
|
//stream::once::<Message, _>(Ok(Message::text("Hello")))
|
||||||
|
rx.forward(tx).map(|_| ()).map_err(|e| {
|
||||||
|
log::error!("Websocket error: {}", e);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let script = warp::path("script.js").map(|| JS);
|
||||||
|
let home = warp::path::end().map(|| warp::reply::html(build_template().to_string()));
|
||||||
|
let routes = warp::get2().and(websocket.or(script).or(home));
|
||||||
|
|
||||||
|
let (_addr, server) =
|
||||||
|
warp::serve(routes).bind_with_graceful_shutdown(addr, shutdown_rx);
|
||||||
|
tokio::run(server);
|
||||||
|
});
|
||||||
|
WebServer {
|
||||||
|
shutdown_chan: shutdown_tx,
|
||||||
|
shutdown_mutex,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown(self) {
|
||||||
|
log::debug!("Web server shutting down...");
|
||||||
|
self.shutdown_chan.send(()).unwrap();
|
||||||
|
let _lock = self.shutdown_mutex.lock().unwrap();
|
||||||
|
log::info!("Web server shutdown");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue