From 9791ef781f0c78ee8d6274dcf097d97080e5eac4 Mon Sep 17 00:00:00 2001 From: Sathwik Matsa Date: Thu, 15 Jul 2021 12:56:27 +0530 Subject: [PATCH 1/4] Add destroy to Drawing to close window --- examples/runtest.rs | 12 +- src/async_drawing.rs | 15 +- src/drawing.rs | 34 ++++- src/ipc_protocol/messages.rs | 13 +- src/ipc_protocol/protocol.rs | 112 ++++++++------ src/renderer_server.rs | 138 ++++++++---------- src/renderer_server/event_loop_notifier.rs | 8 +- src/renderer_server/handlers.rs | 22 +-- .../handlers/destroy_drawing.rs | 7 + src/renderer_server/main.rs | 123 ++++++++-------- .../test_event_loop_notifier.rs | 6 +- 11 files changed, 265 insertions(+), 225 deletions(-) create mode 100644 src/renderer_server/handlers/destroy_drawing.rs diff --git a/examples/runtest.rs b/examples/runtest.rs index 45cd9cd1..82a0226b 100644 --- a/examples/runtest.rs +++ b/examples/runtest.rs @@ -1,18 +1,16 @@ //! This is NOT a real example. This is a test designed to see if we can actually run the turtle //! process -use std::process; - -use turtle::Turtle; +use turtle::Drawing; fn main() { - let mut turtle = Turtle::new(); + let mut drawing = Drawing::new(); + + let mut turtle = drawing.add_turtle(); turtle.set_speed(2); turtle.right(90.0); turtle.forward(50.0); - //TODO: Exiting the process currently doesn't cause the window to get closed. We should add a - // `close(self)` or `quit(self)` method to `Drawing` that closes the window explicitly. - process::exit(0); + drawing.destroy(); } diff --git a/src/async_drawing.rs b/src/async_drawing.rs index 42931ea1..bea8a6a1 100644 --- a/src/async_drawing.rs +++ b/src/async_drawing.rs @@ -1,11 +1,11 @@ use std::fmt::Debug; use std::path::Path; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; -use crate::ipc_protocol::ProtocolClient; use crate::async_turtle::AsyncTurtle; -use crate::{Drawing, Point, Color, Event, ExportError}; +use crate::ipc_protocol::ProtocolClient; +use crate::{Color, Drawing, Event, ExportError, Point}; /// Represents a size /// @@ -71,9 +71,8 @@ impl AsyncDrawing { // of many programs that use the turtle crate. crate::start(); - let client = ProtocolClient::new().await - .expect("unable to create renderer client"); - Self {client} + let client = ProtocolClient::new().await.expect("unable to create renderer client"); + Self { client } } pub async fn add_turtle(&mut self) -> AsyncTurtle { @@ -187,4 +186,8 @@ impl AsyncDrawing { pub async fn debug(&self) -> impl Debug { self.client.debug_drawing().await } + + pub fn destroy(self) { + self.client.destroy(); + } } diff --git a/src/drawing.rs b/src/drawing.rs index 014e9eb1..6be4129b 100644 --- a/src/drawing.rs +++ b/src/drawing.rs @@ -1,9 +1,9 @@ use std::fmt::{self, Debug}; use std::path::Path; -use crate::{Turtle, Color, Point, Size, ExportError}; use crate::async_drawing::AsyncDrawing; use crate::sync_runtime::block_on; +use crate::{Color, ExportError, Point, Size, Turtle}; /// Provides access to properties of the drawing that the turtle is creating /// @@ -70,7 +70,7 @@ impl From for Drawing { fn from(drawing: AsyncDrawing) -> Self { //TODO: There is no way to set `turtles` properly here, but that's okay since it is going // to be removed soon. - Self {drawing, turtles: 1} + Self { drawing, turtles: 1 } } } @@ -627,6 +627,30 @@ impl Drawing { pub fn save_svg>(&self, path: P) -> Result<(), ExportError> { block_on(self.drawing.save_svg(path)) } + + /// Destroys underlying window and drops self. + /// + /// Subsequent commands to turtle, created using [`Drawing::add_turtle`], might panic. + /// + /// ```rust + /// use turtle::Drawing; + /// + /// let mut drawing = Drawing::new(); + /// let mut turtle = drawing.add_turtle(); + /// + /// turtle.set_speed(2); + /// turtle.right(90.0); + /// turtle.forward(50.0); + /// + /// // close window + /// drawing.destroy(); + /// + /// // this will panic! + /// // turtle.forward(100.0) + /// ``` + pub fn destroy(self) { + self.drawing.destroy(); + } } #[cfg(test)] @@ -634,7 +658,9 @@ mod tests { use super::*; #[test] - #[should_panic(expected = "Invalid color: Color { red: NaN, green: 0.0, blue: 0.0, alpha: 0.0 }. See the color module documentation for more information.")] + #[should_panic( + expected = "Invalid color: Color { red: NaN, green: 0.0, blue: 0.0, alpha: 0.0 }. See the color module documentation for more information." + )] fn rejects_invalid_background_color() { let mut drawing = Drawing::new(); drawing.set_background_color(Color { @@ -655,7 +681,7 @@ mod tests { #[test] fn ignores_center_nan_inf() { - let center = Point {x: 5.0, y: 10.0}; + let center = Point { x: 5.0, y: 10.0 }; let mut drawing = Drawing::new(); drawing.set_center(center); diff --git a/src/ipc_protocol/messages.rs b/src/ipc_protocol/messages.rs index 0fdb1f18..ccb76879 100644 --- a/src/ipc_protocol/messages.rs +++ b/src/ipc_protocol/messages.rs @@ -1,10 +1,10 @@ use std::path::PathBuf; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; -use crate::{Color, Point, Speed, Event, Distance, Size}; -use crate::renderer_server::{TurtleId, ExportError}; -use crate::{async_turtle::AngleUnit, radians::Radians, debug}; +use crate::renderer_server::{ExportError, TurtleId}; +use crate::{async_turtle::AngleUnit, debug, radians::Radians}; +use crate::{Color, Distance, Event, Point, Size, Speed}; /// The different kinds of requests that can be sent from a client /// @@ -152,6 +152,11 @@ pub enum ClientRequest { /// /// Response: `ServerResponse::DebugDrawing` DebugDrawing, + + /// Destroys drawing window. + /// + /// Response: N/A + DestroyDrawing, } #[derive(Debug, Serialize, Deserialize)] diff --git a/src/ipc_protocol/protocol.rs b/src/ipc_protocol/protocol.rs index d389773b..4608648d 100644 --- a/src/ipc_protocol/protocol.rs +++ b/src/ipc_protocol/protocol.rs @@ -1,22 +1,13 @@ use std::path::PathBuf; -use crate::renderer_client::RendererClient; -use crate::renderer_server::{TurtleId, ExportError}; use crate::radians::Radians; -use crate::{Distance, Point, Color, Speed, Event, Size, async_turtle::AngleUnit, debug}; +use crate::renderer_client::RendererClient; +use crate::renderer_server::{ExportError, TurtleId}; +use crate::{async_turtle::AngleUnit, debug, Color, Distance, Event, Point, Size, Speed}; use super::{ - ConnectionError, - ClientRequest, - ServerResponse, - ExportFormat, - DrawingProp, - DrawingPropValue, - TurtleProp, - TurtlePropValue, - PenProp, - PenPropValue, - RotationDirection, + ClientRequest, ConnectionError, DrawingProp, DrawingPropValue, ExportFormat, PenProp, PenPropValue, RotationDirection, ServerResponse, + TurtleProp, TurtlePropValue, }; /// A wrapper for `RendererClient` that encodes the the IPC protocol in a type-safe manner @@ -26,7 +17,7 @@ pub struct ProtocolClient { impl From for ProtocolClient { fn from(client: RendererClient) -> Self { - Self {client} + Self { client } } } @@ -137,26 +128,37 @@ impl ProtocolClient { } pub fn drawing_set_background(&self, value: Color) { - debug_assert!(value.is_valid(), "bug: colors should be validated before sending to renderer server"); + debug_assert!( + value.is_valid(), + "bug: colors should be validated before sending to renderer server" + ); self.client.send(ClientRequest::SetDrawingProp(DrawingPropValue::Background(value))) } pub fn drawing_set_center(&self, value: Point) { - debug_assert!(value.is_finite(), "bug: center should be validated before sending to renderer server"); + debug_assert!( + value.is_finite(), + "bug: center should be validated before sending to renderer server" + ); self.client.send(ClientRequest::SetDrawingProp(DrawingPropValue::Center(value))) } pub fn drawing_set_size(&self, value: Size) { - debug_assert!(value.width > 0 && value.height > 0, "bug: size should be validated before sending to renderer server"); + debug_assert!( + value.width > 0 && value.height > 0, + "bug: size should be validated before sending to renderer server" + ); self.client.send(ClientRequest::SetDrawingProp(DrawingPropValue::Size(value))) } pub fn drawing_set_is_maximized(&self, value: bool) { - self.client.send(ClientRequest::SetDrawingProp(DrawingPropValue::IsMaximized(value))) + self.client + .send(ClientRequest::SetDrawingProp(DrawingPropValue::IsMaximized(value))) } pub fn drawing_set_is_fullscreen(&self, value: bool) { - self.client.send(ClientRequest::SetDrawingProp(DrawingPropValue::IsFullscreen(value))) + self.client + .send(ClientRequest::SetDrawingProp(DrawingPropValue::IsFullscreen(value))) } pub fn drawing_reset_center(&self) { @@ -175,7 +177,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::Pen(PenPropValue::IsEnabled(value))) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - }, + } _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -188,7 +190,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::Pen(PenPropValue::Thickness(value))) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - }, + } _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -201,7 +203,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::Pen(PenPropValue::Color(value))) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - }, + } _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -214,7 +216,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::FillColor(value)) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - }, + } _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -227,7 +229,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::IsFilling(value)) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - }, + } _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -240,7 +242,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::Position(value)) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - }, + } _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -253,7 +255,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::Heading(value)) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - }, + } _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -266,7 +268,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::Speed(value)) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - }, + } _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -279,28 +281,45 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::IsVisible(value)) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - }, + } _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } pub fn turtle_pen_set_is_enabled(&self, id: TurtleId, value: bool) { - self.client.send(ClientRequest::SetTurtleProp(id, TurtlePropValue::Pen(PenPropValue::IsEnabled(value)))) + self.client.send(ClientRequest::SetTurtleProp( + id, + TurtlePropValue::Pen(PenPropValue::IsEnabled(value)), + )) } pub fn turtle_pen_set_thickness(&self, id: TurtleId, value: f64) { - debug_assert!(value >= 0.0 && value.is_finite(), "bug: pen size should be validated before sending to renderer server"); - self.client.send(ClientRequest::SetTurtleProp(id, TurtlePropValue::Pen(PenPropValue::Thickness(value)))) + debug_assert!( + value >= 0.0 && value.is_finite(), + "bug: pen size should be validated before sending to renderer server" + ); + self.client.send(ClientRequest::SetTurtleProp( + id, + TurtlePropValue::Pen(PenPropValue::Thickness(value)), + )) } pub fn turtle_pen_set_color(&self, id: TurtleId, value: Color) { - debug_assert!(value.is_valid(), "bug: colors should be validated before sending to renderer server"); - self.client.send(ClientRequest::SetTurtleProp(id, TurtlePropValue::Pen(PenPropValue::Color(value)))) + debug_assert!( + value.is_valid(), + "bug: colors should be validated before sending to renderer server" + ); + self.client + .send(ClientRequest::SetTurtleProp(id, TurtlePropValue::Pen(PenPropValue::Color(value)))) } pub fn turtle_set_fill_color(&self, id: TurtleId, value: Color) { - debug_assert!(value.is_valid(), "bug: colors should be validated before sending to renderer server"); - self.client.send(ClientRequest::SetTurtleProp(id, TurtlePropValue::FillColor(value))) + debug_assert!( + value.is_valid(), + "bug: colors should be validated before sending to renderer server" + ); + self.client + .send(ClientRequest::SetTurtleProp(id, TurtlePropValue::FillColor(value))) } pub fn turtle_set_speed(&self, id: TurtleId, value: Speed) { @@ -308,7 +327,8 @@ impl ProtocolClient { } pub fn turtle_set_is_visible(&self, id: TurtleId, value: bool) { - self.client.send(ClientRequest::SetTurtleProp(id, TurtlePropValue::IsVisible(value))) + self.client + .send(ClientRequest::SetTurtleProp(id, TurtlePropValue::IsVisible(value))) } pub fn turtle_reset_heading(&self, id: TurtleId) { @@ -330,7 +350,7 @@ impl ProtocolClient { match response { ServerResponse::AnimationComplete(recv_id) => { debug_assert_eq!(id, recv_id, "bug: notified of complete animation for incorrect turtle"); - }, + } _ => unreachable!("bug: expected to receive `AnimationComplete` in response to `MoveForward` request"), } } @@ -346,7 +366,7 @@ impl ProtocolClient { match response { ServerResponse::AnimationComplete(recv_id) => { debug_assert_eq!(id, recv_id, "bug: notified of complete animation for incorrect turtle"); - }, + } _ => unreachable!("bug: expected to receive `AnimationComplete` in response to `MoveTo` request"), } } @@ -362,7 +382,7 @@ impl ProtocolClient { match response { ServerResponse::AnimationComplete(recv_id) => { debug_assert_eq!(id, recv_id, "bug: notified of complete animation for incorrect turtle"); - }, + } _ => unreachable!("bug: expected to receive `AnimationComplete` in response to `RotateInPlace` request"), } } @@ -372,7 +392,7 @@ impl ProtocolClient { return; } - let steps = 250; // Arbitrary value for now. + let steps = 250; // Arbitrary value for now. let step = radius.abs() * extent.to_radians() / steps as f64; let rotation = radius.signum() * extent / steps as f64; @@ -406,7 +426,7 @@ impl ProtocolClient { ServerResponse::DebugTurtle(recv_id, state) => { debug_assert_eq!(id, recv_id, "bug: received debug turtle for incorrect turtle"); state - }, + } _ => unreachable!("bug: expected to receive `DebugTurtle` in response to `DebugTurtle` request"), } } @@ -416,10 +436,12 @@ impl ProtocolClient { let response = self.client.recv().await; match response { - ServerResponse::DebugDrawing(state) => { - state - }, + ServerResponse::DebugDrawing(state) => state, _ => unreachable!("bug: expected to receive `DebugDrawing` in response to `DebugDrawing` request"), } } + + pub fn destroy(self) { + self.client.send(ClientRequest::DestroyDrawing); + } } diff --git a/src/renderer_server.rs b/src/renderer_server.rs index 04fdad80..1e70a99d 100644 --- a/src/renderer_server.rs +++ b/src/renderer_server.rs @@ -1,11 +1,11 @@ -mod state; +mod animation; mod app; -mod coords; -mod renderer; mod backend; -mod animation; +mod coords; mod handlers; +mod renderer; mod start; +mod state; cfg_if::cfg_if! { if #[cfg(any(feature = "test", test))] { @@ -24,16 +24,16 @@ pub use renderer::export::ExportError; pub use start::start; use ipc_channel::ipc::IpcError; +use parking_lot::{Mutex, RwLock}; use tokio::sync::mpsc; -use parking_lot::{RwLock, Mutex}; -use crate::ipc_protocol::{ServerSender, ServerOneshotSender, ServerReceiver, ClientRequest}; +use crate::ipc_protocol::{ClientRequest, ServerOneshotSender, ServerReceiver, ServerSender}; use crate::Event; -use app::{SharedApp, App}; -use renderer::display_list::{SharedDisplayList, DisplayList}; -use event_loop_notifier::EventLoopNotifier; use animation::AnimationRunner; +use app::{App, SharedApp}; +use event_loop_notifier::EventLoopNotifier; +use renderer::display_list::{DisplayList, SharedDisplayList}; /// Serves requests from the client forever async fn serve( @@ -45,12 +45,7 @@ async fn serve( mut events_receiver: mpsc::UnboundedReceiver, mut server_shutdown_receiver: mpsc::Receiver<()>, ) { - let anim_runner = AnimationRunner::new( - conn.clone(), - app.clone(), - display_list.clone(), - event_loop.clone(), - ); + let anim_runner = AnimationRunner::new(conn.clone(), app.clone(), display_list.clone(), event_loop.clone()); loop { // This will either receive the next request or end this task @@ -77,7 +72,6 @@ async fn serve( &anim_runner, request, )); - } } @@ -92,84 +86,66 @@ fn dispatch_request( ) -> Result<(), handlers::HandlerError> { use ClientRequest::*; match request { - CreateTurtle => { - handlers::create_turtle(conn, &mut app.write(), event_loop) - }, - - Export(path, format) => { - handlers::export_drawings(conn, &app.read(), &display_list.lock(), &path, format) - }, - - PollEvent => { - handlers::poll_event(conn, events_receiver) - }, - - DrawingProp(prop) => { - handlers::drawing_prop(conn, &app.read(), prop) - }, - SetDrawingProp(prop_value) => { - handlers::set_drawing_prop(&mut app.write(), event_loop, prop_value) - }, - ResetDrawingProp(prop) => { - handlers::reset_drawing_prop(&mut app.write(), event_loop, prop) - }, - - TurtleProp(id, prop) => { - handlers::turtle_prop(conn, &app.read(), id, prop) - }, - SetTurtleProp(id, prop_value) => { - handlers::set_turtle_prop(&mut app.write(), &mut display_list.lock(), event_loop, id, prop_value) - }, - ResetTurtleProp(id, prop) => { - handlers::reset_turtle_prop(&mut app.write(), &mut display_list.lock(), event_loop, id, prop) - }, - ResetTurtle(id) => { - handlers::reset_turtle(&mut app.write(), &mut display_list.lock(), event_loop, id) - }, - - MoveForward(id, distance) => { - handlers::move_forward(conn, &mut app.write(), &mut display_list.lock(), event_loop, anim_runner, id, distance) - }, - MoveTo(id, target_pos) => { - handlers::move_to(conn, &mut app.write(), &mut display_list.lock(), event_loop, anim_runner, id, target_pos) - }, + CreateTurtle => handlers::create_turtle(conn, &mut app.write(), event_loop), + + Export(path, format) => handlers::export_drawings(conn, &app.read(), &display_list.lock(), &path, format), + + PollEvent => handlers::poll_event(conn, events_receiver), + + DrawingProp(prop) => handlers::drawing_prop(conn, &app.read(), prop), + SetDrawingProp(prop_value) => handlers::set_drawing_prop(&mut app.write(), event_loop, prop_value), + ResetDrawingProp(prop) => handlers::reset_drawing_prop(&mut app.write(), event_loop, prop), + + TurtleProp(id, prop) => handlers::turtle_prop(conn, &app.read(), id, prop), + SetTurtleProp(id, prop_value) => handlers::set_turtle_prop(&mut app.write(), &mut display_list.lock(), event_loop, id, prop_value), + ResetTurtleProp(id, prop) => handlers::reset_turtle_prop(&mut app.write(), &mut display_list.lock(), event_loop, id, prop), + ResetTurtle(id) => handlers::reset_turtle(&mut app.write(), &mut display_list.lock(), event_loop, id), + + MoveForward(id, distance) => handlers::move_forward( + conn, + &mut app.write(), + &mut display_list.lock(), + event_loop, + anim_runner, + id, + distance, + ), + MoveTo(id, target_pos) => handlers::move_to( + conn, + &mut app.write(), + &mut display_list.lock(), + event_loop, + anim_runner, + id, + target_pos, + ), RotateInPlace(id, angle, direction) => { handlers::rotate_in_place(conn, &mut app.write(), event_loop, anim_runner, id, angle, direction) - }, - - BeginFill(id) => { - handlers::begin_fill(&mut app.write(), &mut display_list.lock(), event_loop, id) - }, - EndFill(id) => { - handlers::end_fill(&mut app.write(), id) - }, - - ClearAll => { - handlers::clear_all(&mut app.write(), &mut display_list.lock(), event_loop, anim_runner) - }, - ClearTurtle(id) => { - handlers::clear_turtle(&mut app.write(), &mut display_list.lock(), event_loop, id) - }, - - DebugTurtle(id, angle_unit) => { - handlers::debug_turtle(conn, &app.read(), id, angle_unit) - }, - DebugDrawing => { - handlers::debug_drawing(conn, &app.read()) - }, + } + + BeginFill(id) => handlers::begin_fill(&mut app.write(), &mut display_list.lock(), event_loop, id), + EndFill(id) => handlers::end_fill(&mut app.write(), id), + + ClearAll => handlers::clear_all(&mut app.write(), &mut display_list.lock(), event_loop, anim_runner), + ClearTurtle(id) => handlers::clear_turtle(&mut app.write(), &mut display_list.lock(), event_loop, id), + + DebugTurtle(id, angle_unit) => handlers::debug_turtle(conn, &app.read(), id, angle_unit), + DebugDrawing => handlers::debug_drawing(conn, &app.read()), + + DestroyDrawing => handlers::destroy_drawing(event_loop), } } fn handle_handler_result(res: Result<(), handlers::HandlerError>) { use handlers::HandlerError::*; match res { - Ok(_) => {}, + Ok(_) => {} Err(IpcChannelError(err)) => panic!("Error while serializing response: {}", err), // Task managing window has ended, this task will end soon too. //TODO: This potentially leaves the turtle/drawing state in an inconsistent state. Should // we deal with that somehow? Panicking doesn't seem appropriate since this probably isn't // an error, but we should definitely stop processing commands and make sure the process // ends shortly after. - Err(EventLoopClosed(_)) => {}, + Err(EventLoopClosed(_)) => {} } } diff --git a/src/renderer_server/event_loop_notifier.rs b/src/renderer_server/event_loop_notifier.rs index 5010b2e3..c9c56b4a 100644 --- a/src/renderer_server/event_loop_notifier.rs +++ b/src/renderer_server/event_loop_notifier.rs @@ -27,6 +27,8 @@ pub enum MainThreadAction { SetIsMaximized(bool), /// Change the fullscreen state of the window SetIsFullscreen(bool), + /// Exit event loop (close window) + Exit, } /// Notifies the main loop when actions need to take place @@ -37,7 +39,11 @@ pub struct EventLoopNotifier { impl EventLoopNotifier { pub fn new(event_loop: EventLoopProxy) -> Self { - Self {event_loop} + Self { event_loop } + } + + pub fn exit(&self) -> Result<(), EventLoopClosed> { + self.send_action(MainThreadAction::Exit) } pub fn request_redraw(&self) -> Result<(), EventLoopClosed> { diff --git a/src/renderer_server/handlers.rs b/src/renderer_server/handlers.rs index 3c8fda10..a19799a7 100644 --- a/src/renderer_server/handlers.rs +++ b/src/renderer_server/handlers.rs @@ -1,22 +1,24 @@ +mod animation; +mod clear; mod create_turtle; +mod debug; +mod destroy_drawing; +mod drawing_prop; mod export_drawings; +mod fill; mod poll_event; -mod drawing_prop; mod turtle_prop; -mod animation; -mod fill; -mod clear; -mod debug; +pub(crate) use animation::*; +pub(crate) use clear::*; pub(crate) use create_turtle::*; +pub(crate) use debug::*; +pub(crate) use destroy_drawing::*; +pub(crate) use drawing_prop::*; pub(crate) use export_drawings::*; +pub(crate) use fill::*; pub(crate) use poll_event::*; -pub(crate) use drawing_prop::*; pub(crate) use turtle_prop::*; -pub(crate) use animation::*; -pub(crate) use fill::*; -pub(crate) use clear::*; -pub(crate) use debug::*; use thiserror::Error; diff --git a/src/renderer_server/handlers/destroy_drawing.rs b/src/renderer_server/handlers/destroy_drawing.rs new file mode 100644 index 00000000..a99d1dc4 --- /dev/null +++ b/src/renderer_server/handlers/destroy_drawing.rs @@ -0,0 +1,7 @@ +use super::super::event_loop_notifier::EventLoopNotifier; +use super::HandlerError; + +pub(crate) fn destroy_drawing(event_loop: &EventLoopNotifier) -> Result<(), HandlerError> { + event_loop.exit()?; + Ok(()) +} diff --git a/src/renderer_server/main.rs b/src/renderer_server/main.rs index de0c81ce..7710d7cf 100644 --- a/src/renderer_server/main.rs +++ b/src/renderer_server/main.rs @@ -1,41 +1,27 @@ -use std::time::{Instant, Duration}; use std::future::Future; +use std::time::{Duration, Instant}; use glutin::{ - GlProfile, - GlRequest, - ContextBuilder, - WindowedContext, - PossiblyCurrent, dpi::{LogicalSize, PhysicalPosition}, - window::{WindowBuilder, Fullscreen}, - event::{ - Event as GlutinEvent, - StartCause, - WindowEvent, - KeyboardInput, - VirtualKeyCode, - ElementState, - }, + event::{ElementState, Event as GlutinEvent, KeyboardInput, StartCause, VirtualKeyCode, WindowEvent}, event_loop::{ControlFlow, EventLoop}, platform::run_return::EventLoopExtRunReturn, + window::{Fullscreen, WindowBuilder}, + ContextBuilder, GlProfile, GlRequest, PossiblyCurrent, WindowedContext, }; -use tokio::{ - sync::mpsc, - runtime::Handle, -}; +use tokio::{runtime::Handle, sync::mpsc}; +use crate::ipc_protocol::{ConnectionError, ServerReceiver, ServerSender}; use crate::Event; -use crate::ipc_protocol::{ServerSender, ServerReceiver, ConnectionError}; use super::{ - app::{SharedApp, App}, + app::{App, SharedApp}, coords::ScreenPoint, + event_loop_notifier::{EventLoopNotifier, MainThreadAction}, renderer::{ + display_list::{DisplayList, SharedDisplayList}, Renderer, - display_list::{SharedDisplayList, DisplayList}, }, - event_loop_notifier::{EventLoopNotifier, MainThreadAction}, }; /// The maximum rendering FPS allowed @@ -77,7 +63,7 @@ pub fn run_main( handle: Handle, // Polled to establish the server connection - establish_connection: impl Future> + Send + 'static, + establish_connection: impl Future> + Send + 'static, ) { // The state of the drawing and the state/drawings associated with each turtle let app = SharedApp::default(); @@ -105,9 +91,10 @@ pub fn run_main( let window_builder = { let app = app.read(); let drawing = app.drawing(); - WindowBuilder::new() - .with_title(&drawing.title) - .with_inner_size(LogicalSize {width: drawing.width, height: drawing.height}) + WindowBuilder::new().with_title(&drawing.title).with_inner_size(LogicalSize { + width: drawing.width, + height: drawing.height, + }) }; // Create an OpenGL 3.x context for Pathfinder to use @@ -152,43 +139,47 @@ pub fn run_main( establish_connection.take().expect("bug: init event should only occur once"), server_shutdown_receiver.take().expect("bug: init event should only occur once"), ); - }, + } - GlutinEvent::NewEvents(StartCause::ResumeTimeReached {..}) => { + GlutinEvent::NewEvents(StartCause::ResumeTimeReached { .. }) => { // A render was delayed in the `RedrawRequested` so let's try to do it again now that // we have resumed gl_context.window().request_redraw(); - }, + } // Quit if the window is closed or if Esc is pressed and then released GlutinEvent::WindowEvent { event: WindowEvent::CloseRequested, .. - } | GlutinEvent::WindowEvent { + } + | GlutinEvent::WindowEvent { event: WindowEvent::Destroyed, .. - } | GlutinEvent::WindowEvent { - event: WindowEvent::KeyboardInput { - input: KeyboardInput { - state: ElementState::Released, - virtual_keycode: Some(VirtualKeyCode::Escape), + } + | GlutinEvent::WindowEvent { + event: + WindowEvent::KeyboardInput { + input: + KeyboardInput { + state: ElementState::Released, + virtual_keycode: Some(VirtualKeyCode::Escape), + .. + }, .. }, - .. - }, .. } => { *control_flow = ControlFlow::Exit; - }, + } GlutinEvent::WindowEvent { - event: WindowEvent::ScaleFactorChanged {scale_factor, ..}, + event: WindowEvent::ScaleFactorChanged { scale_factor, .. }, .. } => { renderer.set_scale_factor(scale_factor); - }, + } - GlutinEvent::WindowEvent {event, ..} => { + GlutinEvent::WindowEvent { event, .. } => { let scale_factor = renderer.scale_factor(); match event { WindowEvent::Resized(size) => { @@ -197,12 +188,11 @@ pub fn run_main( let mut drawing = app.drawing_mut(); drawing.width = size.width; drawing.height = size.height; - }, + } //TODO: There are currently no events for updating is_maximized, so that property // should not be relied on. https://github.com/rust-windowing/glutin/issues/1298 - - _ => {}, + _ => {} } // Converts to logical coordinates, only locking the drawing if this is actually called @@ -228,32 +218,38 @@ pub fn run_main( // main process ends. This is not a fatal error though so we just ignore it. events_sender.send(event).unwrap_or(()); } - }, + } // Window events are currently sufficient for the turtle event API - GlutinEvent::DeviceEvent {..} => {}, + GlutinEvent::DeviceEvent { .. } => {} GlutinEvent::UserEvent(MainThreadAction::Redraw) => { gl_context.window().request_redraw(); - }, + } GlutinEvent::UserEvent(MainThreadAction::SetTitle(title)) => { gl_context.window().set_title(&title); - }, + } GlutinEvent::UserEvent(MainThreadAction::SetSize(size)) => { gl_context.window().set_inner_size(size); - }, + } GlutinEvent::UserEvent(MainThreadAction::SetIsMaximized(is_maximized)) => { gl_context.window().set_maximized(is_maximized); - }, + } GlutinEvent::UserEvent(MainThreadAction::SetIsFullscreen(is_fullscreen)) => { gl_context.window().set_fullscreen(if is_fullscreen { Some(Fullscreen::Borderless(gl_context.window().current_monitor())) - } else { None }); - }, + } else { + None + }); + } + + GlutinEvent::UserEvent(MainThreadAction::Exit) => { + *control_flow = ControlFlow::Exit; + } GlutinEvent::RedrawRequested(_) => { // Check if we just rendered @@ -273,24 +269,19 @@ pub fn run_main( // // This is why the window has 0 CPU usage when nothing is happening *control_flow = ControlFlow::Wait; - }, + } GlutinEvent::LoopDestroyed => { // Notify the server that it should shutdown, ignoring the error if the channel has // been dropped since that just means that the server task has ended already handle.block_on(server_shutdown.send(())).unwrap_or(()); - }, + } - _ => {}, + _ => {} }); } -fn redraw( - app: &App, - display_list: &DisplayList, - gl_context: &WindowedContext, - renderer: &mut Renderer, -) { +fn redraw(app: &App, display_list: &DisplayList, gl_context: &WindowedContext, renderer: &mut Renderer) { let draw_size = gl_context.window().inner_size(); let drawing = app.drawing(); let turtle_states = app.turtles().map(|(_, turtle)| &turtle.state); @@ -305,12 +296,11 @@ fn spawn_async_server( display_list: SharedDisplayList, event_loop: EventLoopNotifier, events_receiver: mpsc::UnboundedReceiver, - establish_connection: impl Future> + Send + 'static, + establish_connection: impl Future> + Send + 'static, server_shutdown_receiver: mpsc::Receiver<()>, ) { handle.spawn(async { - let (conn_sender, conn_receiver) = establish_connection.await - .expect("unable to establish turtle server connection"); + let (conn_sender, conn_receiver) = establish_connection.await.expect("unable to establish turtle server connection"); super::serve( conn_sender, @@ -320,6 +310,7 @@ fn spawn_async_server( event_loop, events_receiver, server_shutdown_receiver, - ).await; + ) + .await; }); } diff --git a/src/renderer_server/test_event_loop_notifier.rs b/src/renderer_server/test_event_loop_notifier.rs index c12ee61b..0dff7f87 100644 --- a/src/renderer_server/test_event_loop_notifier.rs +++ b/src/renderer_server/test_event_loop_notifier.rs @@ -1,5 +1,5 @@ -use thiserror::Error; use glutin::dpi::LogicalSize; +use thiserror::Error; #[derive(Debug, Clone, Copy, PartialEq, Eq, Error)] #[error("event loop closed while messages were still being sent to it")] @@ -14,6 +14,10 @@ impl EventLoopNotifier { Self {} } + pub fn exit(&self) -> Result<(), EventLoopClosed> { + Ok(()) + } + pub fn request_redraw(&self) -> Result<(), EventLoopClosed> { Ok(()) } From 20e18ec23381f46e730570899be63c933094269f Mon Sep 17 00:00:00 2001 From: Sathwik Matsa Date: Fri, 16 Jul 2021 11:27:10 +0530 Subject: [PATCH 2/4] undo rustfmt --- examples/runtest.rs | 12 +- src/async_drawing.rs | 11 +- src/drawing.rs | 10 +- src/ipc_protocol/messages.rs | 8 +- src/ipc_protocol/protocol.rs | 108 ++++++------- src/renderer_server.rs | 142 +++++++++++------- src/renderer_server/event_loop_notifier.rs | 10 +- src/renderer_server/handlers.rs | 24 +-- src/renderer_server/main.rs | 119 ++++++++------- .../test_event_loop_notifier.rs | 10 +- 10 files changed, 239 insertions(+), 215 deletions(-) diff --git a/examples/runtest.rs b/examples/runtest.rs index 82a0226b..45cd9cd1 100644 --- a/examples/runtest.rs +++ b/examples/runtest.rs @@ -1,16 +1,18 @@ //! This is NOT a real example. This is a test designed to see if we can actually run the turtle //! process -use turtle::Drawing; +use std::process; -fn main() { - let mut drawing = Drawing::new(); +use turtle::Turtle; - let mut turtle = drawing.add_turtle(); +fn main() { + let mut turtle = Turtle::new(); turtle.set_speed(2); turtle.right(90.0); turtle.forward(50.0); - drawing.destroy(); + //TODO: Exiting the process currently doesn't cause the window to get closed. We should add a + // `close(self)` or `quit(self)` method to `Drawing` that closes the window explicitly. + process::exit(0); } diff --git a/src/async_drawing.rs b/src/async_drawing.rs index bea8a6a1..499ef40b 100644 --- a/src/async_drawing.rs +++ b/src/async_drawing.rs @@ -1,11 +1,11 @@ use std::fmt::Debug; use std::path::Path; -use serde::{Deserialize, Serialize}; +use serde::{Serialize, Deserialize}; -use crate::async_turtle::AsyncTurtle; use crate::ipc_protocol::ProtocolClient; -use crate::{Color, Drawing, Event, ExportError, Point}; +use crate::async_turtle::AsyncTurtle; +use crate::{Drawing, Point, Color, Event, ExportError}; /// Represents a size /// @@ -71,8 +71,9 @@ impl AsyncDrawing { // of many programs that use the turtle crate. crate::start(); - let client = ProtocolClient::new().await.expect("unable to create renderer client"); - Self { client } + let client = ProtocolClient::new().await + .expect("unable to create renderer client"); + Self {client} } pub async fn add_turtle(&mut self) -> AsyncTurtle { diff --git a/src/drawing.rs b/src/drawing.rs index 6be4129b..f9fab197 100644 --- a/src/drawing.rs +++ b/src/drawing.rs @@ -1,9 +1,9 @@ use std::fmt::{self, Debug}; use std::path::Path; +use crate::{Turtle, Color, Point, Size, ExportError}; use crate::async_drawing::AsyncDrawing; use crate::sync_runtime::block_on; -use crate::{Color, ExportError, Point, Size, Turtle}; /// Provides access to properties of the drawing that the turtle is creating /// @@ -70,7 +70,7 @@ impl From for Drawing { fn from(drawing: AsyncDrawing) -> Self { //TODO: There is no way to set `turtles` properly here, but that's okay since it is going // to be removed soon. - Self { drawing, turtles: 1 } + Self {drawing, turtles: 1} } } @@ -658,9 +658,7 @@ mod tests { use super::*; #[test] - #[should_panic( - expected = "Invalid color: Color { red: NaN, green: 0.0, blue: 0.0, alpha: 0.0 }. See the color module documentation for more information." - )] + #[should_panic(expected = "Invalid color: Color { red: NaN, green: 0.0, blue: 0.0, alpha: 0.0 }. See the color module documentation for more information.")] fn rejects_invalid_background_color() { let mut drawing = Drawing::new(); drawing.set_background_color(Color { @@ -681,7 +679,7 @@ mod tests { #[test] fn ignores_center_nan_inf() { - let center = Point { x: 5.0, y: 10.0 }; + let center = Point {x: 5.0, y: 10.0}; let mut drawing = Drawing::new(); drawing.set_center(center); diff --git a/src/ipc_protocol/messages.rs b/src/ipc_protocol/messages.rs index ccb76879..ceb975eb 100644 --- a/src/ipc_protocol/messages.rs +++ b/src/ipc_protocol/messages.rs @@ -1,10 +1,10 @@ use std::path::PathBuf; -use serde::{Deserialize, Serialize}; +use serde::{Serialize, Deserialize}; -use crate::renderer_server::{ExportError, TurtleId}; -use crate::{async_turtle::AngleUnit, debug, radians::Radians}; -use crate::{Color, Distance, Event, Point, Size, Speed}; +use crate::{Color, Point, Speed, Event, Distance, Size}; +use crate::renderer_server::{TurtleId, ExportError}; +use crate::{async_turtle::AngleUnit, radians::Radians, debug}; /// The different kinds of requests that can be sent from a client /// diff --git a/src/ipc_protocol/protocol.rs b/src/ipc_protocol/protocol.rs index 4608648d..0ec25550 100644 --- a/src/ipc_protocol/protocol.rs +++ b/src/ipc_protocol/protocol.rs @@ -1,13 +1,22 @@ use std::path::PathBuf; -use crate::radians::Radians; use crate::renderer_client::RendererClient; -use crate::renderer_server::{ExportError, TurtleId}; -use crate::{async_turtle::AngleUnit, debug, Color, Distance, Event, Point, Size, Speed}; +use crate::renderer_server::{TurtleId, ExportError}; +use crate::radians::Radians; +use crate::{Distance, Point, Color, Speed, Event, Size, async_turtle::AngleUnit, debug}; use super::{ - ClientRequest, ConnectionError, DrawingProp, DrawingPropValue, ExportFormat, PenProp, PenPropValue, RotationDirection, ServerResponse, - TurtleProp, TurtlePropValue, + ConnectionError, + ClientRequest, + ServerResponse, + ExportFormat, + DrawingProp, + DrawingPropValue, + TurtleProp, + TurtlePropValue, + PenProp, + PenPropValue, + RotationDirection, }; /// A wrapper for `RendererClient` that encodes the the IPC protocol in a type-safe manner @@ -17,7 +26,7 @@ pub struct ProtocolClient { impl From for ProtocolClient { fn from(client: RendererClient) -> Self { - Self { client } + Self {client} } } @@ -128,37 +137,26 @@ impl ProtocolClient { } pub fn drawing_set_background(&self, value: Color) { - debug_assert!( - value.is_valid(), - "bug: colors should be validated before sending to renderer server" - ); + debug_assert!(value.is_valid(), "bug: colors should be validated before sending to renderer server"); self.client.send(ClientRequest::SetDrawingProp(DrawingPropValue::Background(value))) } pub fn drawing_set_center(&self, value: Point) { - debug_assert!( - value.is_finite(), - "bug: center should be validated before sending to renderer server" - ); + debug_assert!(value.is_finite(), "bug: center should be validated before sending to renderer server"); self.client.send(ClientRequest::SetDrawingProp(DrawingPropValue::Center(value))) } pub fn drawing_set_size(&self, value: Size) { - debug_assert!( - value.width > 0 && value.height > 0, - "bug: size should be validated before sending to renderer server" - ); + debug_assert!(value.width > 0 && value.height > 0, "bug: size should be validated before sending to renderer server"); self.client.send(ClientRequest::SetDrawingProp(DrawingPropValue::Size(value))) } pub fn drawing_set_is_maximized(&self, value: bool) { - self.client - .send(ClientRequest::SetDrawingProp(DrawingPropValue::IsMaximized(value))) + self.client.send(ClientRequest::SetDrawingProp(DrawingPropValue::IsMaximized(value))) } pub fn drawing_set_is_fullscreen(&self, value: bool) { - self.client - .send(ClientRequest::SetDrawingProp(DrawingPropValue::IsFullscreen(value))) + self.client.send(ClientRequest::SetDrawingProp(DrawingPropValue::IsFullscreen(value))) } pub fn drawing_reset_center(&self) { @@ -177,7 +175,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::Pen(PenPropValue::IsEnabled(value))) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - } + }, _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -190,7 +188,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::Pen(PenPropValue::Thickness(value))) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - } + }, _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -203,7 +201,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::Pen(PenPropValue::Color(value))) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - } + }, _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -216,7 +214,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::FillColor(value)) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - } + }, _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -229,7 +227,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::IsFilling(value)) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - } + }, _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -242,7 +240,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::Position(value)) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - } + }, _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -255,7 +253,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::Heading(value)) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - } + }, _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -268,7 +266,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::Speed(value)) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - } + }, _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -281,45 +279,28 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::IsVisible(value)) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - } + }, _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } pub fn turtle_pen_set_is_enabled(&self, id: TurtleId, value: bool) { - self.client.send(ClientRequest::SetTurtleProp( - id, - TurtlePropValue::Pen(PenPropValue::IsEnabled(value)), - )) + self.client.send(ClientRequest::SetTurtleProp(id, TurtlePropValue::Pen(PenPropValue::IsEnabled(value)))) } pub fn turtle_pen_set_thickness(&self, id: TurtleId, value: f64) { - debug_assert!( - value >= 0.0 && value.is_finite(), - "bug: pen size should be validated before sending to renderer server" - ); - self.client.send(ClientRequest::SetTurtleProp( - id, - TurtlePropValue::Pen(PenPropValue::Thickness(value)), - )) + debug_assert!(value >= 0.0 && value.is_finite(), "bug: pen size should be validated before sending to renderer server"); + self.client.send(ClientRequest::SetTurtleProp(id, TurtlePropValue::Pen(PenPropValue::Thickness(value)))) } pub fn turtle_pen_set_color(&self, id: TurtleId, value: Color) { - debug_assert!( - value.is_valid(), - "bug: colors should be validated before sending to renderer server" - ); - self.client - .send(ClientRequest::SetTurtleProp(id, TurtlePropValue::Pen(PenPropValue::Color(value)))) + debug_assert!(value.is_valid(), "bug: colors should be validated before sending to renderer server"); + self.client.send(ClientRequest::SetTurtleProp(id, TurtlePropValue::Pen(PenPropValue::Color(value)))) } pub fn turtle_set_fill_color(&self, id: TurtleId, value: Color) { - debug_assert!( - value.is_valid(), - "bug: colors should be validated before sending to renderer server" - ); - self.client - .send(ClientRequest::SetTurtleProp(id, TurtlePropValue::FillColor(value))) + debug_assert!(value.is_valid(), "bug: colors should be validated before sending to renderer server"); + self.client.send(ClientRequest::SetTurtleProp(id, TurtlePropValue::FillColor(value))) } pub fn turtle_set_speed(&self, id: TurtleId, value: Speed) { @@ -327,8 +308,7 @@ impl ProtocolClient { } pub fn turtle_set_is_visible(&self, id: TurtleId, value: bool) { - self.client - .send(ClientRequest::SetTurtleProp(id, TurtlePropValue::IsVisible(value))) + self.client.send(ClientRequest::SetTurtleProp(id, TurtlePropValue::IsVisible(value))) } pub fn turtle_reset_heading(&self, id: TurtleId) { @@ -350,7 +330,7 @@ impl ProtocolClient { match response { ServerResponse::AnimationComplete(recv_id) => { debug_assert_eq!(id, recv_id, "bug: notified of complete animation for incorrect turtle"); - } + }, _ => unreachable!("bug: expected to receive `AnimationComplete` in response to `MoveForward` request"), } } @@ -366,7 +346,7 @@ impl ProtocolClient { match response { ServerResponse::AnimationComplete(recv_id) => { debug_assert_eq!(id, recv_id, "bug: notified of complete animation for incorrect turtle"); - } + }, _ => unreachable!("bug: expected to receive `AnimationComplete` in response to `MoveTo` request"), } } @@ -382,7 +362,7 @@ impl ProtocolClient { match response { ServerResponse::AnimationComplete(recv_id) => { debug_assert_eq!(id, recv_id, "bug: notified of complete animation for incorrect turtle"); - } + }, _ => unreachable!("bug: expected to receive `AnimationComplete` in response to `RotateInPlace` request"), } } @@ -392,7 +372,7 @@ impl ProtocolClient { return; } - let steps = 250; // Arbitrary value for now. + let steps = 250; // Arbitrary value for now. let step = radius.abs() * extent.to_radians() / steps as f64; let rotation = radius.signum() * extent / steps as f64; @@ -426,7 +406,7 @@ impl ProtocolClient { ServerResponse::DebugTurtle(recv_id, state) => { debug_assert_eq!(id, recv_id, "bug: received debug turtle for incorrect turtle"); state - } + }, _ => unreachable!("bug: expected to receive `DebugTurtle` in response to `DebugTurtle` request"), } } @@ -436,7 +416,9 @@ impl ProtocolClient { let response = self.client.recv().await; match response { - ServerResponse::DebugDrawing(state) => state, + ServerResponse::DebugDrawing(state) => { + state + }, _ => unreachable!("bug: expected to receive `DebugDrawing` in response to `DebugDrawing` request"), } } diff --git a/src/renderer_server.rs b/src/renderer_server.rs index 1e70a99d..54b90354 100644 --- a/src/renderer_server.rs +++ b/src/renderer_server.rs @@ -1,11 +1,11 @@ -mod animation; +mod state; mod app; -mod backend; mod coords; -mod handlers; mod renderer; +mod backend; +mod animation; +mod handlers; mod start; -mod state; cfg_if::cfg_if! { if #[cfg(any(feature = "test", test))] { @@ -24,16 +24,16 @@ pub use renderer::export::ExportError; pub use start::start; use ipc_channel::ipc::IpcError; -use parking_lot::{Mutex, RwLock}; use tokio::sync::mpsc; +use parking_lot::{RwLock, Mutex}; -use crate::ipc_protocol::{ClientRequest, ServerOneshotSender, ServerReceiver, ServerSender}; +use crate::ipc_protocol::{ServerSender, ServerOneshotSender, ServerReceiver, ClientRequest}; use crate::Event; -use animation::AnimationRunner; -use app::{App, SharedApp}; +use app::{SharedApp, App}; +use renderer::display_list::{SharedDisplayList, DisplayList}; use event_loop_notifier::EventLoopNotifier; -use renderer::display_list::{DisplayList, SharedDisplayList}; +use animation::AnimationRunner; /// Serves requests from the client forever async fn serve( @@ -45,7 +45,12 @@ async fn serve( mut events_receiver: mpsc::UnboundedReceiver, mut server_shutdown_receiver: mpsc::Receiver<()>, ) { - let anim_runner = AnimationRunner::new(conn.clone(), app.clone(), display_list.clone(), event_loop.clone()); + let anim_runner = AnimationRunner::new( + conn.clone(), + app.clone(), + display_list.clone(), + event_loop.clone(), + ); loop { // This will either receive the next request or end this task @@ -72,6 +77,7 @@ async fn serve( &anim_runner, request, )); + } } @@ -86,66 +92,88 @@ fn dispatch_request( ) -> Result<(), handlers::HandlerError> { use ClientRequest::*; match request { - CreateTurtle => handlers::create_turtle(conn, &mut app.write(), event_loop), - - Export(path, format) => handlers::export_drawings(conn, &app.read(), &display_list.lock(), &path, format), - - PollEvent => handlers::poll_event(conn, events_receiver), - - DrawingProp(prop) => handlers::drawing_prop(conn, &app.read(), prop), - SetDrawingProp(prop_value) => handlers::set_drawing_prop(&mut app.write(), event_loop, prop_value), - ResetDrawingProp(prop) => handlers::reset_drawing_prop(&mut app.write(), event_loop, prop), - - TurtleProp(id, prop) => handlers::turtle_prop(conn, &app.read(), id, prop), - SetTurtleProp(id, prop_value) => handlers::set_turtle_prop(&mut app.write(), &mut display_list.lock(), event_loop, id, prop_value), - ResetTurtleProp(id, prop) => handlers::reset_turtle_prop(&mut app.write(), &mut display_list.lock(), event_loop, id, prop), - ResetTurtle(id) => handlers::reset_turtle(&mut app.write(), &mut display_list.lock(), event_loop, id), - - MoveForward(id, distance) => handlers::move_forward( - conn, - &mut app.write(), - &mut display_list.lock(), - event_loop, - anim_runner, - id, - distance, - ), - MoveTo(id, target_pos) => handlers::move_to( - conn, - &mut app.write(), - &mut display_list.lock(), - event_loop, - anim_runner, - id, - target_pos, - ), + CreateTurtle => { + handlers::create_turtle(conn, &mut app.write(), event_loop) + }, + + Export(path, format) => { + handlers::export_drawings(conn, &app.read(), &display_list.lock(), &path, format) + }, + + PollEvent => { + handlers::poll_event(conn, events_receiver) + }, + + DrawingProp(prop) => { + handlers::drawing_prop(conn, &app.read(), prop) + }, + SetDrawingProp(prop_value) => { + handlers::set_drawing_prop(&mut app.write(), event_loop, prop_value) + }, + ResetDrawingProp(prop) => { + handlers::reset_drawing_prop(&mut app.write(), event_loop, prop) + }, + + TurtleProp(id, prop) => { + handlers::turtle_prop(conn, &app.read(), id, prop) + }, + SetTurtleProp(id, prop_value) => { + handlers::set_turtle_prop(&mut app.write(), &mut display_list.lock(), event_loop, id, prop_value) + }, + ResetTurtleProp(id, prop) => { + handlers::reset_turtle_prop(&mut app.write(), &mut display_list.lock(), event_loop, id, prop) + }, + ResetTurtle(id) => { + handlers::reset_turtle(&mut app.write(), &mut display_list.lock(), event_loop, id) + }, + + MoveForward(id, distance) => { + handlers::move_forward(conn, &mut app.write(), &mut display_list.lock(), event_loop, anim_runner, id, distance) + }, + MoveTo(id, target_pos) => { + handlers::move_to(conn, &mut app.write(), &mut display_list.lock(), event_loop, anim_runner, id, target_pos) + }, RotateInPlace(id, angle, direction) => { handlers::rotate_in_place(conn, &mut app.write(), event_loop, anim_runner, id, angle, direction) - } - - BeginFill(id) => handlers::begin_fill(&mut app.write(), &mut display_list.lock(), event_loop, id), - EndFill(id) => handlers::end_fill(&mut app.write(), id), - - ClearAll => handlers::clear_all(&mut app.write(), &mut display_list.lock(), event_loop, anim_runner), - ClearTurtle(id) => handlers::clear_turtle(&mut app.write(), &mut display_list.lock(), event_loop, id), - - DebugTurtle(id, angle_unit) => handlers::debug_turtle(conn, &app.read(), id, angle_unit), - DebugDrawing => handlers::debug_drawing(conn, &app.read()), - - DestroyDrawing => handlers::destroy_drawing(event_loop), + }, + + BeginFill(id) => { + handlers::begin_fill(&mut app.write(), &mut display_list.lock(), event_loop, id) + }, + EndFill(id) => { + handlers::end_fill(&mut app.write(), id) + }, + + ClearAll => { + handlers::clear_all(&mut app.write(), &mut display_list.lock(), event_loop, anim_runner) + }, + ClearTurtle(id) => { + handlers::clear_turtle(&mut app.write(), &mut display_list.lock(), event_loop, id) + }, + + DebugTurtle(id, angle_unit) => { + handlers::debug_turtle(conn, &app.read(), id, angle_unit) + }, + DebugDrawing => { + handlers::debug_drawing(conn, &app.read()) + }, + + DestroyDrawing => { + handlers::destroy_drawing(event_loop) + }, } } fn handle_handler_result(res: Result<(), handlers::HandlerError>) { use handlers::HandlerError::*; match res { - Ok(_) => {} + Ok(_) => {}, Err(IpcChannelError(err)) => panic!("Error while serializing response: {}", err), // Task managing window has ended, this task will end soon too. //TODO: This potentially leaves the turtle/drawing state in an inconsistent state. Should // we deal with that somehow? Panicking doesn't seem appropriate since this probably isn't // an error, but we should definitely stop processing commands and make sure the process // ends shortly after. - Err(EventLoopClosed(_)) => {} + Err(EventLoopClosed(_)) => {}, } } diff --git a/src/renderer_server/event_loop_notifier.rs b/src/renderer_server/event_loop_notifier.rs index c9c56b4a..9be612fd 100644 --- a/src/renderer_server/event_loop_notifier.rs +++ b/src/renderer_server/event_loop_notifier.rs @@ -39,11 +39,7 @@ pub struct EventLoopNotifier { impl EventLoopNotifier { pub fn new(event_loop: EventLoopProxy) -> Self { - Self { event_loop } - } - - pub fn exit(&self) -> Result<(), EventLoopClosed> { - self.send_action(MainThreadAction::Exit) + Self {event_loop} } pub fn request_redraw(&self) -> Result<(), EventLoopClosed> { @@ -66,6 +62,10 @@ impl EventLoopNotifier { self.send_action(MainThreadAction::SetIsFullscreen(is_fullscreen)) } + pub fn exit(&self) -> Result<(), EventLoopClosed> { + self.send_action(MainThreadAction::Exit) + } + fn send_action(&self, action: MainThreadAction) -> Result<(), EventLoopClosed> { Ok(self.event_loop.send_event(action)?) } diff --git a/src/renderer_server/handlers.rs b/src/renderer_server/handlers.rs index a19799a7..11167e20 100644 --- a/src/renderer_server/handlers.rs +++ b/src/renderer_server/handlers.rs @@ -1,24 +1,24 @@ -mod animation; -mod clear; mod create_turtle; -mod debug; -mod destroy_drawing; -mod drawing_prop; mod export_drawings; -mod fill; mod poll_event; +mod drawing_prop; mod turtle_prop; +mod animation; +mod fill; +mod clear; +mod debug; +mod destroy_drawing; -pub(crate) use animation::*; -pub(crate) use clear::*; pub(crate) use create_turtle::*; -pub(crate) use debug::*; -pub(crate) use destroy_drawing::*; -pub(crate) use drawing_prop::*; pub(crate) use export_drawings::*; -pub(crate) use fill::*; pub(crate) use poll_event::*; +pub(crate) use drawing_prop::*; pub(crate) use turtle_prop::*; +pub(crate) use animation::*; +pub(crate) use fill::*; +pub(crate) use clear::*; +pub(crate) use debug::*; +pub(crate) use destroy_drawing::*; use thiserror::Error; diff --git a/src/renderer_server/main.rs b/src/renderer_server/main.rs index 7710d7cf..2b4ba320 100644 --- a/src/renderer_server/main.rs +++ b/src/renderer_server/main.rs @@ -1,27 +1,41 @@ +use std::time::{Instant, Duration}; use std::future::Future; -use std::time::{Duration, Instant}; use glutin::{ + GlProfile, + GlRequest, + ContextBuilder, + WindowedContext, + PossiblyCurrent, dpi::{LogicalSize, PhysicalPosition}, - event::{ElementState, Event as GlutinEvent, KeyboardInput, StartCause, VirtualKeyCode, WindowEvent}, + window::{WindowBuilder, Fullscreen}, + event::{ + Event as GlutinEvent, + StartCause, + WindowEvent, + KeyboardInput, + VirtualKeyCode, + ElementState, + }, event_loop::{ControlFlow, EventLoop}, platform::run_return::EventLoopExtRunReturn, - window::{Fullscreen, WindowBuilder}, - ContextBuilder, GlProfile, GlRequest, PossiblyCurrent, WindowedContext, }; -use tokio::{runtime::Handle, sync::mpsc}; +use tokio::{ + sync::mpsc, + runtime::Handle, +}; -use crate::ipc_protocol::{ConnectionError, ServerReceiver, ServerSender}; use crate::Event; +use crate::ipc_protocol::{ServerSender, ServerReceiver, ConnectionError}; use super::{ - app::{App, SharedApp}, + app::{SharedApp, App}, coords::ScreenPoint, - event_loop_notifier::{EventLoopNotifier, MainThreadAction}, renderer::{ - display_list::{DisplayList, SharedDisplayList}, Renderer, + display_list::{SharedDisplayList, DisplayList}, }, + event_loop_notifier::{EventLoopNotifier, MainThreadAction}, }; /// The maximum rendering FPS allowed @@ -63,7 +77,7 @@ pub fn run_main( handle: Handle, // Polled to establish the server connection - establish_connection: impl Future> + Send + 'static, + establish_connection: impl Future> + Send + 'static, ) { // The state of the drawing and the state/drawings associated with each turtle let app = SharedApp::default(); @@ -91,10 +105,9 @@ pub fn run_main( let window_builder = { let app = app.read(); let drawing = app.drawing(); - WindowBuilder::new().with_title(&drawing.title).with_inner_size(LogicalSize { - width: drawing.width, - height: drawing.height, - }) + WindowBuilder::new() + .with_title(&drawing.title) + .with_inner_size(LogicalSize {width: drawing.width, height: drawing.height}) }; // Create an OpenGL 3.x context for Pathfinder to use @@ -139,47 +152,43 @@ pub fn run_main( establish_connection.take().expect("bug: init event should only occur once"), server_shutdown_receiver.take().expect("bug: init event should only occur once"), ); - } + }, - GlutinEvent::NewEvents(StartCause::ResumeTimeReached { .. }) => { + GlutinEvent::NewEvents(StartCause::ResumeTimeReached {..}) => { // A render was delayed in the `RedrawRequested` so let's try to do it again now that // we have resumed gl_context.window().request_redraw(); - } + }, // Quit if the window is closed or if Esc is pressed and then released GlutinEvent::WindowEvent { event: WindowEvent::CloseRequested, .. - } - | GlutinEvent::WindowEvent { + } | GlutinEvent::WindowEvent { event: WindowEvent::Destroyed, .. - } - | GlutinEvent::WindowEvent { - event: - WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Released, - virtual_keycode: Some(VirtualKeyCode::Escape), - .. - }, + } | GlutinEvent::WindowEvent { + event: WindowEvent::KeyboardInput { + input: KeyboardInput { + state: ElementState::Released, + virtual_keycode: Some(VirtualKeyCode::Escape), .. }, + .. + }, .. } => { *control_flow = ControlFlow::Exit; - } + }, GlutinEvent::WindowEvent { - event: WindowEvent::ScaleFactorChanged { scale_factor, .. }, + event: WindowEvent::ScaleFactorChanged {scale_factor, ..}, .. } => { renderer.set_scale_factor(scale_factor); - } + }, - GlutinEvent::WindowEvent { event, .. } => { + GlutinEvent::WindowEvent {event, ..} => { let scale_factor = renderer.scale_factor(); match event { WindowEvent::Resized(size) => { @@ -188,11 +197,12 @@ pub fn run_main( let mut drawing = app.drawing_mut(); drawing.width = size.width; drawing.height = size.height; - } + }, //TODO: There are currently no events for updating is_maximized, so that property // should not be relied on. https://github.com/rust-windowing/glutin/issues/1298 - _ => {} + + _ => {}, } // Converts to logical coordinates, only locking the drawing if this is actually called @@ -218,34 +228,32 @@ pub fn run_main( // main process ends. This is not a fatal error though so we just ignore it. events_sender.send(event).unwrap_or(()); } - } + }, // Window events are currently sufficient for the turtle event API - GlutinEvent::DeviceEvent { .. } => {} + GlutinEvent::DeviceEvent {..} => {}, GlutinEvent::UserEvent(MainThreadAction::Redraw) => { gl_context.window().request_redraw(); - } + }, GlutinEvent::UserEvent(MainThreadAction::SetTitle(title)) => { gl_context.window().set_title(&title); - } + }, GlutinEvent::UserEvent(MainThreadAction::SetSize(size)) => { gl_context.window().set_inner_size(size); - } + }, GlutinEvent::UserEvent(MainThreadAction::SetIsMaximized(is_maximized)) => { gl_context.window().set_maximized(is_maximized); - } + }, GlutinEvent::UserEvent(MainThreadAction::SetIsFullscreen(is_fullscreen)) => { gl_context.window().set_fullscreen(if is_fullscreen { Some(Fullscreen::Borderless(gl_context.window().current_monitor())) - } else { - None - }); - } + } else { None }); + }, GlutinEvent::UserEvent(MainThreadAction::Exit) => { *control_flow = ControlFlow::Exit; @@ -269,19 +277,24 @@ pub fn run_main( // // This is why the window has 0 CPU usage when nothing is happening *control_flow = ControlFlow::Wait; - } + }, GlutinEvent::LoopDestroyed => { // Notify the server that it should shutdown, ignoring the error if the channel has // been dropped since that just means that the server task has ended already handle.block_on(server_shutdown.send(())).unwrap_or(()); - } + }, - _ => {} + _ => {}, }); } -fn redraw(app: &App, display_list: &DisplayList, gl_context: &WindowedContext, renderer: &mut Renderer) { +fn redraw( + app: &App, + display_list: &DisplayList, + gl_context: &WindowedContext, + renderer: &mut Renderer, +) { let draw_size = gl_context.window().inner_size(); let drawing = app.drawing(); let turtle_states = app.turtles().map(|(_, turtle)| &turtle.state); @@ -296,11 +309,12 @@ fn spawn_async_server( display_list: SharedDisplayList, event_loop: EventLoopNotifier, events_receiver: mpsc::UnboundedReceiver, - establish_connection: impl Future> + Send + 'static, + establish_connection: impl Future> + Send + 'static, server_shutdown_receiver: mpsc::Receiver<()>, ) { handle.spawn(async { - let (conn_sender, conn_receiver) = establish_connection.await.expect("unable to establish turtle server connection"); + let (conn_sender, conn_receiver) = establish_connection.await + .expect("unable to establish turtle server connection"); super::serve( conn_sender, @@ -310,7 +324,6 @@ fn spawn_async_server( event_loop, events_receiver, server_shutdown_receiver, - ) - .await; + ).await; }); } diff --git a/src/renderer_server/test_event_loop_notifier.rs b/src/renderer_server/test_event_loop_notifier.rs index 0dff7f87..637fe518 100644 --- a/src/renderer_server/test_event_loop_notifier.rs +++ b/src/renderer_server/test_event_loop_notifier.rs @@ -1,5 +1,5 @@ -use glutin::dpi::LogicalSize; use thiserror::Error; +use glutin::dpi::LogicalSize; #[derive(Debug, Clone, Copy, PartialEq, Eq, Error)] #[error("event loop closed while messages were still being sent to it")] @@ -14,10 +14,6 @@ impl EventLoopNotifier { Self {} } - pub fn exit(&self) -> Result<(), EventLoopClosed> { - Ok(()) - } - pub fn request_redraw(&self) -> Result<(), EventLoopClosed> { Ok(()) } @@ -37,4 +33,8 @@ impl EventLoopNotifier { pub fn set_is_fullscreen(&self, _is_fullscreen: bool) -> Result<(), EventLoopClosed> { Ok(()) } + + pub fn exit(&self) -> Result<(), EventLoopClosed> { + Ok(()) + } } From 7eb689537199bf70348a8373a2cc34ad66736511 Mon Sep 17 00:00:00 2001 From: Sathwik Matsa Date: Fri, 16 Jul 2021 12:59:02 +0530 Subject: [PATCH 3/4] Mark Drawing::destroy() as unstable --- src/drawing.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/drawing.rs b/src/drawing.rs index f9fab197..37d01282 100644 --- a/src/drawing.rs +++ b/src/drawing.rs @@ -648,6 +648,8 @@ impl Drawing { /// // this will panic! /// // turtle.forward(100.0) /// ``` + #[cfg(feature = "unstable")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] pub fn destroy(self) { self.drawing.destroy(); } From 7e3bbb94b37548361c94af7f88eaf3746f30c2a5 Mon Sep 17 00:00:00 2001 From: Sathwik Matsa Date: Fri, 16 Jul 2021 13:43:40 +0530 Subject: [PATCH 4/4] update runtest.rs example with Drawing::destroy() --- examples/runtest.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/runtest.rs b/examples/runtest.rs index 45cd9cd1..ee5b23ae 100644 --- a/examples/runtest.rs +++ b/examples/runtest.rs @@ -1,18 +1,18 @@ //! This is NOT a real example. This is a test designed to see if we can actually run the turtle //! process +// To run, use the command: cargo run --features unstable --example runtest +#[cfg(all(not(feature = "unstable")))] +compile_error!("This example relies on unstable features. Run with `--features unstable`"); -use std::process; - -use turtle::Turtle; +use turtle::Drawing; fn main() { - let mut turtle = Turtle::new(); + let mut drawing = Drawing::new(); + let mut turtle = drawing.add_turtle(); turtle.set_speed(2); turtle.right(90.0); turtle.forward(50.0); - //TODO: Exiting the process currently doesn't cause the window to get closed. We should add a - // `close(self)` or `quit(self)` method to `Drawing` that closes the window explicitly. - process::exit(0); + drawing.destroy(); }