diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 8710360..5662409 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -72,7 +72,7 @@ impl Editor { RedrawEvent::ModeChange { mode_index } => self.cursor.change_mode(mode_index, &self.defined_styles), RedrawEvent::BusyStart => self.cursor.enabled = false, RedrawEvent::BusyStop => self.cursor.enabled = true, - RedrawEvent::Flush => REDRAW_SCHEDULER.request_redraw(), + RedrawEvent::Flush => REDRAW_SCHEDULER.queue_next_frame(), RedrawEvent::Resize { width, height, .. } => self.resize((width, height)), RedrawEvent::DefaultColorsSet { colors } => self.default_colors = colors, RedrawEvent::HighlightAttributesDefine { id, style } => { self.defined_styles.insert(id, style); }, diff --git a/src/redraw_scheduler.rs b/src/redraw_scheduler.rs index eabf57a..eff5e36 100644 --- a/src/redraw_scheduler.rs +++ b/src/redraw_scheduler.rs @@ -1,44 +1,81 @@ use std::sync::{Arc, Mutex}; -use std::time::Instant; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::time::{Duration, Instant}; +use std::thread; use skulpin::winit::window::Window; -use tokio::runtime::Runtime; -use tokio::time::{Instant as TokioInstant, delay_until}; lazy_static! { pub static ref REDRAW_SCHEDULER: RedrawScheduler = RedrawScheduler::new(); } pub struct RedrawScheduler { - runtime: Runtime, - window: Mutex>> // Swap to some atomic type + window: Mutex>>, // Would prefer not to have to lock this every time. + frame_queued: AtomicBool, + scheduled_frame: Mutex> +} + +pub fn redraw_loop() { + thread::spawn(|| { + loop { + let frame_start = Instant::now(); + + let request_redraw = { + if REDRAW_SCHEDULER.frame_queued.load(Ordering::Relaxed) { + REDRAW_SCHEDULER.frame_queued.store(false, Ordering::Relaxed); + true + } else { + let mut next_scheduled_frame = REDRAW_SCHEDULER.scheduled_frame.lock().unwrap(); + if let Some(scheduled_frame) = next_scheduled_frame.clone() { + if scheduled_frame < frame_start { + *next_scheduled_frame = None; + true + } else { + false + } + } else { + false + } + } + }; + + if request_redraw { + let window = REDRAW_SCHEDULER.window.lock().unwrap(); + if let Some(window) = &*window { + window.request_redraw(); + } + } + + if let Some(time_to_sleep) = Duration::from_secs_f32(1.0 / 60.0).checked_sub(Instant::now() - frame_start) { + thread::sleep(time_to_sleep) + } + } + }); } impl RedrawScheduler { pub fn new() -> RedrawScheduler { + redraw_loop(); RedrawScheduler { - runtime: Runtime::new().unwrap(), - window: Mutex::new(None) + window: Mutex::new(None), + frame_queued: AtomicBool::new(false), + scheduled_frame: Mutex::new(None) } } - pub fn schedule(&self, time: Instant) { - let window = { - self.window.lock().unwrap().clone() - }; - - if let Some(window) = window { - self.runtime.spawn(async move { - delay_until(TokioInstant::from_std(time)).await; - window.request_redraw(); - }); + pub fn schedule(&self, new_scheduled: Instant) { + let mut scheduled_frame = self.scheduled_frame.lock().unwrap(); + if let Some(previous_scheduled) = scheduled_frame.clone() { + if new_scheduled < previous_scheduled { + *scheduled_frame = Some(new_scheduled); + } + } else { + *scheduled_frame = Some(new_scheduled); } } - pub fn request_redraw(&self) { - if let Some(window) = self.window.lock().unwrap().as_ref() { - window.request_redraw(); - } + pub fn queue_next_frame(&self) { + self.frame_queued.store(true, Ordering::Relaxed); } pub fn set_window(&self, window: &Arc){ diff --git a/src/renderer/cursor_renderer.rs b/src/renderer/cursor_renderer.rs index 6030ef5..e065af6 100644 --- a/src/renderer/cursor_renderer.rs +++ b/src/renderer/cursor_renderer.rs @@ -4,6 +4,7 @@ use skulpin::skia_safe::{Canvas, Paint, Path, Point}; use crate::renderer::CachingShaper; use crate::editor::{EDITOR, Colors, Cursor, CursorShape}; +use crate::redraw_scheduler::REDRAW_SCHEDULER; const AVERAGE_MOTION_PERCENTAGE: f32 = 0.7; const MOTION_PERCENTAGE_SPREAD: f32 = 0.5; @@ -33,7 +34,7 @@ impl BlinkStatus { } } - pub fn update_status(&mut self, new_cursor: &Cursor) -> (bool, Option) { + pub fn update_status(&mut self, new_cursor: &Cursor) -> bool { if self.previous_cursor.is_none() || new_cursor != self.previous_cursor.as_ref().unwrap() { self.previous_cursor = Some(new_cursor.clone()); self.last_transition = Instant::now(); @@ -47,7 +48,7 @@ impl BlinkStatus { if new_cursor.blinkwait == Some(0) || new_cursor.blinkoff == Some(0) || new_cursor.blinkon == Some(0) { - return (true, None); + return true; } let delay = match self.state { @@ -65,17 +66,20 @@ impl BlinkStatus { self.last_transition = Instant::now(); } - ( - match self.state { - BlinkState::Waiting | BlinkState::Off => false, - BlinkState::On => true - }, - (match self.state { - BlinkState::Waiting => new_cursor.blinkwait, - BlinkState::Off => new_cursor.blinkoff, - BlinkState::On => new_cursor.blinkon - }).map(|delay| self.last_transition + Duration::from_millis(delay)) - ) + let scheduled_frame = (match self.state { + BlinkState::Waiting => new_cursor.blinkwait, + BlinkState::Off => new_cursor.blinkoff, + BlinkState::On => new_cursor.blinkon + }).map(|delay| self.last_transition + Duration::from_millis(delay)); + + if let Some(scheduled_frame) = scheduled_frame { + REDRAW_SCHEDULER.schedule(scheduled_frame); + } + + match self.state { + BlinkState::Waiting | BlinkState::Off => false, + BlinkState::On => true + } } } @@ -171,8 +175,8 @@ impl CursorRenderer { cursor: Cursor, default_colors: &Colors, font_width: f32, font_height: f32, paint: &mut Paint, shaper: &mut CachingShaper, - canvas: &mut Canvas) -> (bool, Option) { - let (render, scheduled_update) = self.blink_status.update_status(&cursor); + canvas: &mut Canvas) { + let render = self.blink_status.update_status(&cursor); self.previous_position = { let editor = EDITOR.lock().unwrap(); @@ -224,6 +228,10 @@ impl CursorRenderer { } } + if animating { + REDRAW_SCHEDULER.queue_next_frame(); + } + if cursor.enabled && render { // Draw Background paint.set_color(cursor.background(&default_colors).to_color()); @@ -240,7 +248,6 @@ impl CursorRenderer { // Draw foreground paint.set_color(cursor.foreground(&default_colors).to_color()); - let editor = EDITOR.lock().unwrap(); canvas.save(); canvas.clip_path(&path, None, Some(false)); @@ -250,7 +257,5 @@ impl CursorRenderer { } canvas.restore(); } - - (animating, scheduled_update) } } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 839368c..10b4a5b 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,5 +1,3 @@ -use std::time::Instant; - use skulpin::CoordinateSystemHelper; use skulpin::skia_safe::{Canvas, Paint, Surface, Budgeted, Rect, colors}; use skulpin::skia_safe::gpu::SurfaceOrigin; @@ -13,13 +11,6 @@ pub use caching_shaper::CachingShaper; use cursor_renderer::CursorRenderer; use crate::editor::{EDITOR, Style, Colors}; -#[derive(new)] -pub struct DrawResult { - pub is_animating: bool, - pub font_changed: bool, - pub scheduled_update: Option -} - pub struct Renderer { surface: Option, paint: Paint, @@ -105,7 +96,7 @@ impl Renderer { canvas.restore(); } - pub fn draw(&mut self, gpu_canvas: &mut Canvas, coordinate_system_helper: &CoordinateSystemHelper) -> DrawResult { + pub fn draw(&mut self, gpu_canvas: &mut Canvas, coordinate_system_helper: &CoordinateSystemHelper) -> bool { let ((draw_commands, should_clear), default_colors, cursor, font_name, font_size) = { let mut editor = EDITOR.lock().unwrap(); ( @@ -156,12 +147,12 @@ impl Renderer { self.surface = Some(surface); - let (cursor_animating, scheduled_cursor_update) = self.cursor_renderer.draw( + self.cursor_renderer.draw( cursor, &default_colors, self.font_width, self.font_height, &mut self.paint, &mut self.shaper, gpu_canvas); - DrawResult::new(!draw_commands.is_empty() || cursor_animating, font_changed, scheduled_cursor_update) + font_changed } } diff --git a/src/window.rs b/src/window.rs index c573ecd..1c9a523 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,5 +1,5 @@ -use std::sync::{Arc, Mutex}; -use std::time::{Duration, Instant}; +use std::sync::Arc; +use std::time::Instant; use image::{load_from_memory, GenericImageView, Pixel}; use skulpin::{CoordinateSystem, RendererBuilder, PresentMode}; @@ -7,9 +7,9 @@ use skulpin::skia_safe::icu; use skulpin::winit::dpi::LogicalSize; use skulpin::winit::event::{ElementState, Event, MouseScrollDelta, StartCause, WindowEvent}; use skulpin::winit::event_loop::{ControlFlow, EventLoop}; -use skulpin::winit::window::{Icon, Window, WindowBuilder}; +use skulpin::winit::window::{Icon, WindowBuilder}; -use crate::bridge::{construct_keybinding_string, BRIDGE, Bridge, UiCommand}; +use crate::bridge::{construct_keybinding_string, BRIDGE, UiCommand}; use crate::renderer::Renderer; use crate::redraw_scheduler::REDRAW_SCHEDULER; use crate::INITIAL_DIMENSIONS; @@ -18,8 +18,6 @@ use crate::INITIAL_DIMENSIONS; #[folder = "assets/"] struct Asset; -const EXTRA_LIVE_FRAMES: usize = 10; - fn handle_new_grid_size(new_size: LogicalSize, renderer: &Renderer) { if new_size.width > 0.0 && new_size.height > 0.0 { let new_width = ((new_size.width + 1.0) as f32 / renderer.font_width) as u64; @@ -72,14 +70,9 @@ pub fn ui_loop() { let mut mouse_down = false; let mut mouse_pos = (0, 0); - let mut live_frames = 0; - let mut frame_start = Instant::now(); event_loop.run(move |event, _window_target, control_flow| { match event { - Event::NewEvents(StartCause::Init) | - Event::NewEvents(StartCause::ResumeTimeReached { .. }) => { - window.request_redraw() - }, + Event::NewEvents(StartCause::Init) => window.request_redraw(), Event::WindowEvent { event: WindowEvent::CloseRequested, @@ -90,7 +83,7 @@ pub fn ui_loop() { event: WindowEvent::Resized(new_size), .. } => { - handle_new_grid_size(window.inner_size(), &renderer) + handle_new_grid_size(new_size, &renderer) }, Event::WindowEvent { @@ -174,30 +167,12 @@ pub fn ui_loop() { } Event::RedrawRequested { .. } => { - frame_start = Instant::now(); if let Err(e) = skulpin_renderer.draw(&window, |canvas, coordinate_system_helper| { - let draw_result = renderer.draw(canvas, coordinate_system_helper); - if draw_result.is_animating { - live_frames = EXTRA_LIVE_FRAMES; - } else { - if live_frames > 0 { - live_frames = live_frames - 1; - } - } - - if draw_result.font_changed { + if renderer.draw(canvas, coordinate_system_helper) { handle_new_grid_size(window.inner_size(), &renderer) } - if live_frames > 0 { - *control_flow = ControlFlow::WaitUntil(frame_start + Duration::from_secs_f32(1.0 / 60.0)); - } else { - *control_flow = ControlFlow::Wait; - } - - if let Some(scheduled_update) = draw_result.scheduled_update { - REDRAW_SCHEDULER.schedule(scheduled_update); - } + *control_flow = ControlFlow::Wait; }) { println!("Error during draw: {:?}", e); *control_flow = ControlFlow::Exit