#[cfg(target_arch = "wasm32")]
compile_error!("This module should not be included when compiling to wasm");
use std::env;
use std::io::{self, Write};
use std::process;
use std::sync::mpsc::{self, TryRecvError};
use std::thread;
use crate::app::TurtleApp;
use crate::messenger::{self, Disconnected};
use crate::query::{DrawingCommand, Query, Request, Response, StateUpdate};
use crate::query::DrawingCommand::{BeginFill, EndFill};
use crate::renderer::Renderer;
use crate::Event;
pub fn start() {
if env::var("RUN_TURTLE_CANVAS").unwrap_or_else(|_| "".to_owned()) == "true" {
main();
unreachable!("bug: renderer loop did not exit after finishing");
}
}
fn main() {
let app = TurtleApp::new();
let (drawing_tx, drawing_rx) = mpsc::channel();
let (events_tx, events_rx) = mpsc::channel();
let (running_tx, running_rx) = mpsc::channel();
let thread_app = app.clone();
let handle = thread::spawn(move || {
read_queries_forever(thread_app, drawing_tx, events_rx, running_tx);
});
Renderer::new(app).run(drawing_rx, events_tx);
match running_rx.try_recv() {
Ok(_) => unreachable!("bug: running channel should always be empty"),
Err(mpsc::TryRecvError::Empty) => process::exit(0),
Err(mpsc::TryRecvError::Disconnected) => match handle.join() {
Ok(_) => process::exit(0),
Err(_) => process::exit(1),
},
}
}
fn read_queries_forever(
mut app: TurtleApp,
drawing_tx: mpsc::Sender<DrawingCommand>,
events_rx: mpsc::Receiver<Event>,
_running_tx: mpsc::Sender<()>,
) {
let stdin = io::stdin();
let mut stdout = io::stdout();
messenger::read_forever(
stdin,
"bug: unable to read data from stdin",
"bug: failed to read command from turtle process",
|query| {
handle_query(query, &mut app, &events_rx, &drawing_tx).and_then(|resp| match resp {
Some(ref response) => send_response(&mut stdout, response),
None => Ok(()),
})
},
);
}
#[cfg(any(feature = "test", test))]
pub(crate) fn handle_query_for_test_use_only(
query: Query,
app: &mut TurtleApp,
events_rx: &mpsc::Receiver<Event>,
drawing_tx: &mpsc::Sender<DrawingCommand>,
) -> Result<Option<Response>, Disconnected> {
handle_query(query, app, events_rx, drawing_tx)
}
fn handle_query(
query: Query,
app: &mut TurtleApp,
events_rx: &mpsc::Receiver<Event>,
drawing_tx: &mpsc::Sender<DrawingCommand>,
) -> Result<Option<Response>, Disconnected> {
match query {
Query::Request(req) => handle_request(req, &app, &events_rx),
Query::Update(update) => handle_update(update, app),
Query::Drawing(cmd) => {
match cmd {
BeginFill(_) => {
app.turtle_mut().is_filling = true;
},
EndFill => {
app.turtle_mut().is_filling = false;
}
_ => {},
}
drawing_tx.send(cmd).map(|_| None).map_err(|_| Disconnected)
},
}
}
fn handle_request(request: Request, app: &TurtleApp, events_rx: &mpsc::Receiver<Event>) -> Result<Option<Response>, Disconnected> {
use Request::*;
Ok(Some(match request {
TurtleState => Response::TurtleState((*app.turtle()).clone()),
DrawingState => Response::DrawingState((*app.drawing()).clone()),
Event => Response::Event(match events_rx.try_recv() {
Ok(event) => Some(event),
Err(TryRecvError::Empty) => None,
Err(TryRecvError::Disconnected) => return Err(Disconnected),
}),
}))
}
fn handle_update(update: StateUpdate, app: &mut TurtleApp) -> Result<Option<Response>, Disconnected> {
use StateUpdate::*;
match update {
TurtleState(turtle) => *app.turtle_mut() = turtle,
DrawingState(drawing) => *app.drawing_mut() = drawing,
TemporaryPath(path) => app.set_temporary_path(path),
}
Ok(None)
}
fn send_response<W: Write>(writer: W, response: &Response) -> Result<(), Disconnected> {
messenger::send(writer, response, "bug: unable to write final newline when sending response")
}