flo_draw 0.3.0

Hassle-free windowed 2D graphics rendering
Documentation
use super::events::*;
use super::glutin_thread::*;
use super::render_window::*;
use super::window_properties::*;
use super::glutin_thread_event::*;

use flo_canvas::*;
use flo_stream::*;
use flo_binding::*;
use flo_render::*;
use flo_render_canvas::*;

use ::desync::*;

use futures::prelude::*;
use futures::task::{Poll, Context};

use std::mem;
use std::pin::*;
use std::sync::*;
use std::time::{Duration, Instant};

const MAX_BATCH_TIME: Duration = Duration::from_nanos(1_000_000_000 / 60);

///
/// Structure used to store the current state of the canvas renderer
///
struct RendererState {
    /// The renderer for the canvas
    renderer:       CanvasRenderer,

    /// The transformation from window coordinates to canvas coordinates
    window_transform: Option<Transform2D>,

    /// The scale factor of the canvas
    scale:          f64,

    /// The width of the canvas
    width:          f64,

    /// The height of the canvas
    height:         f64,
}

impl RendererState {
    ///
    /// Updates the window transform for this state
    ///
    fn update_window_transform(&mut self) -> Transform2D {
        // Fetch the window tranform from the canvas, and invert it to get the transform from window coordinates to canvas coordinates
        let window_transform    = self.renderer.get_window_transform().invert().unwrap();

        // Window coordinates are inverted compared to canvas coordinates
        let window_transform    = Transform2D::scale(1.0, -1.0) * window_transform;
        let window_transform    = window_transform * Transform2D::translate(0.0, -self.height as _);

        // Update the value of the transform in the state
        self.window_transform   = Some(window_transform);
        window_transform
    }
}

///
/// Creates a drawing target that will render to a window
///
pub fn create_drawing_window<'a, TProperties: 'a+FloWindowProperties>(window_properties: TProperties) -> DrawingTarget {
    let (target, _events) = create_drawing_window_with_events(window_properties);

    // Dropping the events will stop the window from blocking when they're not handled
    target
}

///
/// Creates a drawing target that will render to a window, along with a stream of events from that window
///
pub fn create_drawing_window_with_events<'a, TProperties: 'a+FloWindowProperties>(window_properties: TProperties) -> (DrawingTarget, impl Clone+Send+Stream<Item=DrawEvent>) {
    let (width, height)     = window_properties.size().get();

    // Create the canvas
    let (target, stream)    = DrawingTarget::new();
    target.draw(|gc| {
        // Default window layout is 1:1 for the requested window size
        gc.clear_canvas(Color::Rgba(1.0, 1.0, 1.0, 1.0));
        gc.canvas_height(height as _);
        gc.center_region(0.0, 0.0, width as _, height as _);
    });

    // Get the stream of drawing instructions (and gather them into batches)
    let target_stream       = stream;
    let target_stream       = drawing_without_dashed_lines(target_stream);
    let target_stream       = drawing_with_laid_out_text(target_stream);
    let target_stream       = drawing_with_text_as_paths(target_stream);
    let target_stream       = BatchedStream { stream: Some(target_stream), frame_count: 0, waiting: vec![] };

    // Create the events stream
    let events              = create_drawing_window_from_stream(target_stream, window_properties);

    // Return the result
    (target, events)
}

///
/// Creates a canvas that will render to a window
///
/// Canvases differ from drawing targets in that they store the vector representation of what they're drawing, and
/// can send their rendering to multiple targets if necessary
///
pub fn create_canvas_window<'a, TProperties: 'a+FloWindowProperties>(window_properties: TProperties) -> Canvas {
    let (canvas, _events) = create_canvas_window_with_events(window_properties);

    // Dropping the events will stop the window from blocking when they're not handled
    canvas
}

///
/// Creates a drawing target that will render to a window, along with a stream of events from that window
///
pub fn create_canvas_window_with_events<'a, TProperties: 'a+FloWindowProperties>(window_properties: TProperties) -> (Canvas, impl Clone+Send+Stream<Item=DrawEvent>) {
    let (width, height)     = window_properties.size().get();

    // Create the canvas
    let canvas              = Canvas::new();
    canvas.draw(|gc| {
        // Default window layout is 1:1 for the requested window size
        gc.clear_canvas(Color::Rgba(1.0, 1.0, 1.0, 1.0));
        gc.canvas_height(height as _);
        gc.center_region(0.0, 0.0, width as _, height as _);
    });

    // Get the stream of drawing instructions (and gather them into batches)
    let canvas_stream       = canvas.stream();
    let canvas_stream       = drawing_without_dashed_lines(canvas_stream);
    let canvas_stream       = drawing_with_laid_out_text(canvas_stream);
    let canvas_stream       = drawing_with_text_as_paths(canvas_stream);
    let canvas_stream       = BatchedStream { stream: Some(canvas_stream), frame_count: 0, waiting: vec![] };

    // Create the events stream
    let events              = create_drawing_window_from_stream(canvas_stream, window_properties);

    // Return the result
    (canvas, events)
}

///
/// Creates a drawing window that will render a stream of drawing instructions
///
pub fn create_drawing_window_from_stream<'a, DrawStream: 'static+Send+Unpin+Stream<Item=Vec<Draw>>, TProperties: 'a+FloWindowProperties>(canvas_stream: DrawStream, window_properties: TProperties) -> impl Clone+Send+Stream<Item=DrawEvent> {
    // Create a static copy of the window properties bindings
    let window_properties               = WindowProperties::from(&window_properties);

    // Create a render window
    let (render_actions, render_events) = create_render_window(window_properties);

    // Create a canvas renderer
    let renderer                        = CanvasRenderer::new();
    let renderer                        = RendererState { renderer: renderer, window_transform: None, scale: 1.0, width: 1.0, height: 1.0 };
    let renderer                        = Arc::new(Desync::new(renderer));
    let mut render_events               = render_events;

    // We republish the events, so we can add our own canvas events
    let mut canvas_events               = Publisher::new(1000);
    let window_events                   = canvas_events.subscribe();

    // Run the main canvas event loop as a process on the glutin thread
    glutin_thread().send_event(GlutinThreadEvent::RunProcess(Box::new(move || async move {
        // Handle events until the first 'redraw' event arrives (or stop if closed)
        loop {
            if let Some(event) = render_events.next().await {
                canvas_events.publish(event.clone()).await;

                if let DrawEvent::Redraw = event {
                    // Begin the main event loop
                    // We've read nothing from the canvas yet so we can drop this event as the first canvas read will trigger a redraw anyway
                    break;
                }

                if let DrawEvent::Closed = event {
                    // Stop if the window is closed
                    return;
                }

                // Handle the next event (until the first 'redraw', we're receiving things like the window size in preparation for the next event)
                let mut event_actions = render_actions.republish();
                renderer.future_sync(move |state| async move { 
                    handle_window_event(state, event, &mut |actions| event_actions.publish(actions)).await; 
                }.boxed()).await.ok();
            } else {
                // Ran out of events
                return;
            }
        }

        // For the main event loop, we're always processing the window events, but alternate between reading from the canvas 
        // and waiting for the frame to render. We stop once there are no more events.
        let render_events       = render_events.ready_chunks(1000);
        let mut canvas_updates  = CanvasUpdateStream {
            draw_stream:            Some(canvas_stream),
            event_stream:           render_events,
            waiting_frame_count:    0
        };

        // The window transform is used to track pointer events: it's invalidated when the size changes or the canvas is updated
        let mut window_transform            = None;
        let mut window_transform_invalid    = false;

        loop {
            // Retrieve the next canvas update
            match canvas_updates.next().await {
                Some(CanvasUpdate::DrawEvents(events)) => {
                    // Update the window transform if it is invalidated
                    if window_transform_invalid {
                        window_transform            = renderer.future_sync(|state| async move { state.window_transform }.boxed()).await.unwrap();
                        window_transform_invalid    = false;
                    }

                    // Process the events
                    for evt in events.iter() {
                        // Republish the event (adding the location on the canvas if necessary)
                        match evt {
                            DrawEvent::Pointer(action, pointer_id, pointer_state) => {
                                let mut pointer_state = pointer_state.clone();
                                
                                if let Some(window_transform) = &window_transform {
                                    let (x, y)                          = pointer_state.location_in_window;
                                    let (x, y)                          = (x as _, y as _);
                                    let (cx, cy)                        = window_transform.transform_point(x, y);
                                    pointer_state.location_in_canvas    = Some((cx as _, cy as _));
                                }

                                canvas_events.publish(DrawEvent::Pointer(*action, *pointer_id, pointer_state)).await;
                            }

                            _ => { canvas_events.publish(evt.clone()).await; }
                        }

                        // Closing the window immediately terminates the event loop, a new frame event reduces the waiting frame count
                        match evt {
                            DrawEvent::Closed       => { return; }
                            DrawEvent::NewFrame     => { if canvas_updates.waiting_frame_count > 0 { canvas_updates.waiting_frame_count -= 1; } },
                            DrawEvent::Redraw       => { window_transform_invalid = true; }
                            DrawEvent::Resize(_, _) => { window_transform_invalid = true; }
                            _                       => { }
                        }
                    }

                    // Handle the events on the renderer thread
                    let mut event_actions   = render_actions.republish();
                    let new_events          = renderer.future_sync(move |state| async move { 
                        let mut new_events = vec![];

                        for event in events.into_iter() {
                            // Handle the event
                            new_events.extend(handle_window_event(state, event, &mut |actions| event_actions.publish(actions)).await);
                        }

                        new_events
                    }.boxed()).await.unwrap_or_else(|_| vec![]);

                    // Send any new events to the canvas events publisher
                    for new_event in new_events.into_iter() {
                        canvas_events.publish(new_event).await;
                    }
                }

                Some(CanvasUpdate::Drawing(drawing)) => {
                     // Received some drawing commands to forward to the canvas (which has rendered its previous frame)
                    let mut event_actions   = render_actions.republish();
                    let mut canvas_events   = canvas_events.republish();

                    renderer.future_desync(move |state| async move {
                        // Wait for any pending render actions to clear the queue before trying to generate new ones
                        event_actions.when_empty().await;

                        // Ask the renderer to process the drawing instructions into render instructions
                        let render_actions = state.renderer.draw(drawing.into_iter()).collect::<Vec<_>>().await;

                        // Send an update that the canvas transform has changed
                        let window_transform    = state.update_window_transform();
                        canvas_events.publish(DrawEvent::CanvasTransform(window_transform)).await;

                        // Send the render actions to the window once they're ready
                        event_actions.publish(render_actions).await;
                    }.boxed());

                    window_transform_invalid = true;
                    
                    // Don't read any more from the canvas until the frame has finished rendering
                    canvas_updates.waiting_frame_count += 1;
                }

                None => {
                    // The main event loop has finished: stop processing events
                    return;
                }
            }
        }
    }.boxed_local())));

    // Return the events
    window_events
}

///
/// Handles an event from the window
///
/// The return value is any extra events to synthesize as a result of the initial event
///
fn handle_window_event<'a, SendFuture, SendRenderActionsFn>(state: &'a mut RendererState, event: DrawEvent, send_render_actions: &'a mut SendRenderActionsFn) -> impl 'a+Send+Future<Output=Vec<DrawEvent>> 
where 
SendRenderActionsFn:    Send+FnMut(Vec<RenderAction>) -> SendFuture,
SendFuture:             Send+Future<Output=()> {
    async move {
        match event {
            DrawEvent::Redraw                   => { 
                // Drawing nothing will regenerate the current contents of the renderer
                let redraw = state.renderer.draw(vec![].into_iter()).collect::<Vec<_>>().await;
                send_render_actions(redraw).await;

                let window_transform    = state.update_window_transform();
                vec![DrawEvent::CanvasTransform(window_transform)]
            },

            DrawEvent::Scale(new_scale)         => {
                state.scale = new_scale;

                let width           = state.width as f32;
                let height          = state.height as f32;
                let scale           = state.scale as f32;

                state.renderer.set_viewport(0.0..width, 0.0..height, width, height, scale);

                vec![]
            }

            DrawEvent::Resize(width, height)    => { 
                state.width         = width;
                state.height        = height;

                let width           = state.width as f32;
                let height          = state.height as f32;
                let scale           = state.scale as f32;

                state.renderer.set_viewport(0.0..width, 0.0..height, width, height, scale); 

                vec![]
            }

            DrawEvent::NewFrame                 => { vec![] }
            DrawEvent::Closed                   => { vec![] }
            DrawEvent::CanvasTransform(_)       => { vec![] }
            DrawEvent::Pointer(_, _, _)         => { vec![] }
            DrawEvent::KeyDown(_, _)            => { vec![] }
            DrawEvent::KeyUp(_, _)              => { vec![] }
        }
    }
}

///
/// Stream that takes a canvas stream and batches as many draw requests as possible
///
struct BatchedStream<TStream>
where TStream: Stream<Item=Draw> {
    /// Items that have been fetched and are waiting to be send
    waiting: Vec<TStream::Item>,

    /// The number of times StartFrame has been called
    frame_count: usize,

    // Stream of individual draw events
    stream: Option<TStream>
}

impl<TStream> Stream for BatchedStream<TStream>
where TStream: Unpin+Stream<Item=Draw> {
    type Item = Vec<TStream::Item>;

    fn poll_next(self: Pin<&mut Self>, context: &mut Context) -> Poll<Option<Vec<TStream::Item>>> {
        let this        = self.get_mut();
        let this_stream = &mut this.stream;
        let waiting     = &mut this.waiting;
        let frame_count = &mut this.frame_count;
        let start_time  = Instant::now();

        match this_stream {
            None                =>  Poll::Ready(None), 
            Some(stream) => {
                // Poll the canvas stream until there are no more items to fetch
                let mut batch           = mem::take(waiting);
                let mut frame_offset    = 0;

                loop {
                    // Fill up the batch
                    match stream.poll_next_unpin(context) {
                        Poll::Ready(None)       => {
                            *this_stream = None;
                            break;
                        }

                        Poll::Ready(Some(Draw::StartFrame)) => {
                            *frame_count += 1;
                            batch.push(Draw::StartFrame);
                        }

                        Poll::Ready(Some(Draw::ShowFrame)) => {
                            if *frame_count > 0 {
                                *frame_count -= 1;
                            }

                            batch.push(Draw::ShowFrame);

                            if *frame_count == 0 {
                                frame_offset = batch.len();

                                if Instant::now().duration_since(start_time) >= MAX_BATCH_TIME {
                                    break;
                                }
                            }
                        }

                        Poll::Ready(Some(Draw::ClearCanvas(colour))) => {
                            *frame_count = 0;
                            batch.push(Draw::ClearCanvas(colour));
                        }

                        Poll::Ready(Some(draw)) => {
                            batch.push(draw)
                        }

                        Poll::Pending           => {
                            break;
                        }
                    }
                }

                if batch.len() == 0 && this_stream.is_none() {
                    // Stream finished with no more items
                    Poll::Ready(None)
                } else if batch.len() == 0 && this_stream.is_some() {
                    // No items were fetched for this batch
                    Poll::Pending
                } else {
                    // Batched up some drawing commands
                    if *frame_count == 0 {
                        // Not paused on a frame
                        Poll::Ready(Some(batch))
                    } else {
                        // Draw everything up until the most recent 'ShowFrame'
                        *waiting = batch.split_off(frame_offset);

                        if batch.len() == 0 {
                            Poll::Pending
                        } else {
                            Poll::Ready(Some(batch))
                        }
                    }
                }
            }
        }
    }
}

///
/// Update events that can be passed to the canvas
///
enum CanvasUpdate {
    /// New drawing actions
    Drawing(Vec<Draw>),

    /// Events from the window
    DrawEvents(Vec<DrawEvent>)
}

///
/// Stream that generates canvas update events
///
/// We avoid reading drawing events if we're waiting for a frame to render (this means that if the canvas
/// turns out to be expensive to render, we won't waste time tessellating frames that will never actually
/// show up)
///
struct CanvasUpdateStream<TDrawStream, TEventStream> {
    draw_stream:            Option<TDrawStream>,
    event_stream:           TEventStream,

    waiting_frame_count:    usize
}

impl<TDrawStream, TEventStream> Stream for CanvasUpdateStream<TDrawStream, TEventStream> 
where 
TDrawStream:    Unpin+Stream<Item=Vec<Draw>>,
TEventStream:   Unpin+Stream<Item=Vec<DrawEvent>> {
    type Item = CanvasUpdate;

    fn poll_next(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        // Events get priority
        match self.event_stream.poll_next_unpin(context) {
            Poll::Ready(Some(events))   => { return Poll::Ready(Some(CanvasUpdate::DrawEvents(events))); }
            Poll::Ready(None)           => { return Poll::Ready(None); }
            Poll::Pending               => { }
        }

        // We only poll the canvas stream if we're not waiting for frame events
        if self.waiting_frame_count == 0 {
            // The canvas stream can get closed, in which case it will be set to 'None'
            if let Some(draw_stream) = self.draw_stream.as_mut() {
                match draw_stream.poll_next_unpin(context) {
                    Poll::Ready(Some(drawing))  => { return Poll::Ready(Some(CanvasUpdate::Drawing(drawing))); }
                    Poll::Ready(None)           => { self.draw_stream = None; }
                    Poll::Pending               => { }
                }
            }
        }

        // No events are ready yet
        Poll::Pending
    }
}