From 26169f84c07113fbd7b5d55f3f4206c9c0a02afc Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Wed, 5 Jan 2022 00:58:48 -0800 Subject: [PATCH] Add event aggregator and remove manual usage of channels. Also add redraw editor command on resume --- src/bridge/handler.rs | 42 +--- src/bridge/mod.rs | 28 +-- src/bridge/ui_commands.rs | 11 +- src/editor/draw_command_batcher.rs | 13 +- src/editor/mod.rs | 320 ++++++++++++++--------------- src/editor/window.rs | 36 +--- src/event_aggregator.rs | 75 +++++++ src/main.rs | 40 +--- src/renderer/mod.rs | 40 ++-- src/renderer/rendered_window.rs | 38 +++- src/window/keyboard_manager.rs | 11 +- src/window/mod.rs | 93 ++++----- src/window/mouse_manager.rs | 47 ++--- 13 files changed, 377 insertions(+), 417 deletions(-) create mode 100644 src/event_aggregator.rs diff --git a/src/bridge/handler.rs b/src/bridge/handler.rs index 5edb16a..b81502f 100644 --- a/src/bridge/handler.rs +++ b/src/bridge/handler.rs @@ -1,37 +1,24 @@ -use std::sync::Arc; - use async_trait::async_trait; use log::trace; use nvim_rs::{Handler, Neovim}; -use parking_lot::Mutex; use rmpv::Value; use tokio::task; -use super::events::{parse_redraw_event, RedrawEvent}; +use super::events::parse_redraw_event; #[cfg(windows)] use super::ui_commands::{ParallelCommand, UiCommand}; use crate::bridge::TxWrapper; -use crate::channel_utils::*; +use crate::editor::EditorCommand; use crate::error_handling::ResultPanicExplanation; +use crate::event_aggregator::EVENT_AGGREGATOR; use crate::settings::SETTINGS; #[derive(Clone)] -pub struct NeovimHandler { - #[cfg(windows)] - ui_command_sender: Arc>>, - redraw_event_sender: Arc>>, -} +pub struct NeovimHandler {} impl NeovimHandler { - pub fn new( - #[cfg(windows)] ui_command_sender: LoggingTx, - redraw_event_sender: LoggingTx, - ) -> NeovimHandler { - NeovimHandler { - #[cfg(windows)] - ui_command_sender: Arc::new(Mutex::new(ui_command_sender)), - redraw_event_sender: Arc::new(Mutex::new(redraw_event_sender)), - } + pub fn new() -> Self { + Self {} } } @@ -47,10 +34,6 @@ impl Handler for NeovimHandler { ) { trace!("Neovim notification: {:?}", &event_name); - #[cfg(windows)] - let ui_command_sender = self.ui_command_sender.clone(); - - let redraw_event_sender = self.redraw_event_sender.clone(); task::spawn_blocking(move || match event_name.as_ref() { "redraw" => { for events in arguments { @@ -58,8 +41,7 @@ impl Handler for NeovimHandler { .unwrap_or_explained_panic("Could not parse event from neovim"); for parsed_event in parsed_events { - let redraw_event_sender = redraw_event_sender.lock(); - redraw_event_sender.send(parsed_event).ok(); + EVENT_AGGREGATOR.send(EditorCommand::NeovimRedrawEvent(parsed_event)); } } } @@ -68,17 +50,11 @@ impl Handler for NeovimHandler { } #[cfg(windows)] "neovide.register_right_click" => { - let ui_command_sender = ui_command_sender.lock(); - ui_command_sender - .send(ParallelCommand::RegisterRightClick.into()) - .ok(); + EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::RegisterRightClick)); } #[cfg(windows)] "neovide.unregister_right_click" => { - let ui_command_sender = ui_command_sender.lock(); - ui_command_sender - .send(ParallelCommand::UnregisterRightClick.into()) - .ok(); + EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::UnregisterRightClick)); } _ => {} }); diff --git a/src/bridge/mod.rs b/src/bridge/mod.rs index e0b38c6..cea0465 100644 --- a/src/bridge/mod.rs +++ b/src/bridge/mod.rs @@ -13,9 +13,7 @@ use nvim_rs::UiAttachOptions; use rmpv::Value; use tokio::process::Command; use tokio::runtime::Runtime; -use tokio::sync::mpsc::UnboundedReceiver; -use crate::channel_utils::*; use crate::running_tracker::*; use crate::settings::*; use crate::{cmd_line::CmdLineSettings, error_handling::ResultPanicExplanation}; @@ -153,15 +151,8 @@ fn connection_mode() -> ConnectionMode { } } -async fn start_neovim_runtime( - #[cfg(windows)] ui_command_sender: LoggingTx, - ui_command_receiver: UnboundedReceiver, - redraw_event_sender: LoggingTx, -) { - #[cfg(windows)] - let handler = NeovimHandler::new(ui_command_sender.clone(), redraw_event_sender.clone()); - #[cfg(not(windows))] - let handler = NeovimHandler::new(redraw_event_sender.clone()); +async fn start_neovim_runtime() { + let handler = NeovimHandler::new(); let (nvim, io_handler) = match connection_mode() { ConnectionMode::Child => create::new_child_cmd(&mut create_nvim_command(), handler).await, ConnectionMode::RemoteTcp(address) => create::new_tcp(address, handler).await, @@ -284,7 +275,7 @@ async fn start_neovim_runtime( let nvim = Arc::new(nvim); - start_ui_command_handler(ui_command_receiver, nvim.clone()); + start_ui_command_handler(nvim.clone()); SETTINGS.read_initial_values(&nvim).await; SETTINGS.setup_changed_listeners(&nvim).await; } @@ -293,17 +284,8 @@ pub struct Bridge { _runtime: Runtime, // Necessary to keep runtime running } -pub fn start_bridge( - #[cfg(windows)] ui_command_sender: LoggingTx, - ui_command_receiver: UnboundedReceiver, - redraw_event_sender: LoggingTx, -) -> Bridge { +pub fn start_bridge() -> Bridge { let runtime = Runtime::new().unwrap(); - runtime.spawn(start_neovim_runtime( - #[cfg(windows)] - ui_command_sender, - ui_command_receiver, - redraw_event_sender, - )); + runtime.spawn(start_neovim_runtime()); Bridge { _runtime: runtime } } diff --git a/src/bridge/ui_commands.rs b/src/bridge/ui_commands.rs index 7dd7241..d28a84e 100644 --- a/src/bridge/ui_commands.rs +++ b/src/bridge/ui_commands.rs @@ -5,9 +5,10 @@ use log::error; use log::trace; use nvim_rs::{call_args, rpc::model::IntoVal, Neovim}; -use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; +use tokio::sync::mpsc::unbounded_channel; use crate::bridge::TxWrapper; +use crate::event_aggregator::EVENT_AGGREGATOR; use crate::running_tracker::RUNNING_TRACKER; #[cfg(windows)] use crate::windows_utils::{ @@ -18,7 +19,7 @@ use crate::windows_utils::{ // includes keyboard and mouse input which would cause problems if sent out of order. // // When in doubt, use Parallel Commands. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub enum SerialCommand { Keyboard(String), MouseButton { @@ -236,13 +237,11 @@ impl From for UiCommand { } } -pub fn start_ui_command_handler( - mut ui_command_receiver: UnboundedReceiver, - nvim: Arc>, -) { +pub fn start_ui_command_handler(nvim: Arc>) { let (serial_tx, mut serial_rx) = unbounded_channel::(); let ui_command_nvim = nvim.clone(); tokio::spawn(async move { + let mut ui_command_receiver = EVENT_AGGREGATOR.register_event::(); while RUNNING_TRACKER.is_running() { match ui_command_receiver.recv().await { Some(UiCommand::Serial(serial_command)) => serial_tx diff --git a/src/editor/draw_command_batcher.rs b/src/editor/draw_command_batcher.rs index d9f01d9..ae05539 100644 --- a/src/editor/draw_command_batcher.rs +++ b/src/editor/draw_command_batcher.rs @@ -1,23 +1,20 @@ use std::sync::mpsc::{channel, Receiver, SendError, Sender}; use super::DrawCommand; -use crate::channel_utils::*; +use crate::event_aggregator::EVENT_AGGREGATOR; pub struct DrawCommandBatcher { window_draw_command_sender: Sender, window_draw_command_receiver: Receiver, - - batched_draw_command_sender: LoggingSender>, } impl DrawCommandBatcher { - pub fn new(batched_draw_command_sender: LoggingSender>) -> DrawCommandBatcher { + pub fn new() -> DrawCommandBatcher { let (sender, receiver) = channel(); DrawCommandBatcher { window_draw_command_sender: sender, window_draw_command_receiver: receiver, - batched_draw_command_sender, } } @@ -25,8 +22,8 @@ impl DrawCommandBatcher { self.window_draw_command_sender.send(draw_command) } - pub fn send_batch(&self) -> Result<(), SendError>> { - let batch = self.window_draw_command_receiver.try_iter().collect(); - self.batched_draw_command_sender.send(batch) + pub fn send_batch(&self) { + let batch: Vec = self.window_draw_command_receiver.try_iter().collect(); + EVENT_AGGREGATOR.send(batch); } } diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 1b79ca7..430b59c 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -9,11 +9,12 @@ use std::sync::Arc; use std::thread; use log::{error, trace}; -use tokio::sync::mpsc::UnboundedReceiver; -use crate::bridge::{EditorMode, GuiOption, RedrawEvent, WindowAnchor}; -use crate::channel_utils::*; +use crate::bridge::{GuiOption, RedrawEvent, WindowAnchor}; +use crate::event_aggregator::EVENT_AGGREGATOR; use crate::redraw_scheduler::REDRAW_SCHEDULER; +use crate::renderer::DrawCommand; +use crate::window::WindowCommand; pub use cursor::{Cursor, CursorMode, CursorShape}; pub use draw_command_batcher::DrawCommandBatcher; pub use grid::CharacterGrid; @@ -46,24 +47,10 @@ impl WindowAnchor { } } -#[derive(Debug)] -pub enum DrawCommand { - CloseWindow(u64), - Window { - grid_id: u64, - command: WindowDrawCommand, - }, - UpdateCursor(Cursor), - FontChanged(String), - DefaultStyleChanged(Style), - ModeChanged(EditorMode), -} - -#[derive(Debug)] -pub enum WindowCommand { - TitleChanged(String), - SetMouseEnabled(bool), - ListAvailableFonts, +#[derive(Clone, Debug)] +pub enum EditorCommand { + NeovimRedrawEvent(RedrawEvent), + RedrawScreen, } pub struct Editor { @@ -72,158 +59,150 @@ pub struct Editor { pub defined_styles: HashMap>, pub mode_list: Vec, pub draw_command_batcher: Arc, - pub window_command_sender: LoggingSender, } impl Editor { - pub fn new( - batched_draw_command_sender: LoggingSender>, - window_command_sender: LoggingSender, - ) -> Editor { + pub fn new() -> Editor { Editor { windows: HashMap::new(), cursor: Cursor::new(), defined_styles: HashMap::new(), mode_list: Vec::new(), - draw_command_batcher: Arc::new(DrawCommandBatcher::new(batched_draw_command_sender)), - window_command_sender, + draw_command_batcher: Arc::new(DrawCommandBatcher::new()), } } - pub fn handle_redraw_event(&mut self, event: RedrawEvent) { - match event { - RedrawEvent::SetTitle { title } => { - self.window_command_sender - .send(WindowCommand::TitleChanged(title)) - .ok(); - } - RedrawEvent::ModeInfoSet { cursor_modes } => self.mode_list = cursor_modes, - RedrawEvent::OptionSet { gui_option } => self.set_option(gui_option), - RedrawEvent::ModeChange { mode, mode_index } => { - if let Some(cursor_mode) = self.mode_list.get(mode_index as usize) { - self.cursor.change_mode(cursor_mode, &self.defined_styles); + pub fn handle_editor_command(&mut self, command: EditorCommand) { + match command { + EditorCommand::NeovimRedrawEvent(event) => match event { + RedrawEvent::SetTitle { title } => { + EVENT_AGGREGATOR.send(WindowCommand::TitleChanged(title)); } - self.draw_command_batcher - .queue(DrawCommand::ModeChanged(mode)) - .ok(); - } - RedrawEvent::MouseOn => { - self.window_command_sender - .send(WindowCommand::SetMouseEnabled(true)) - .ok(); - } - RedrawEvent::MouseOff => { - self.window_command_sender - .send(WindowCommand::SetMouseEnabled(false)) - .ok(); - } - RedrawEvent::BusyStart => { - trace!("Cursor off"); - self.cursor.enabled = false; - } - RedrawEvent::BusyStop => { - trace!("Cursor on"); - self.cursor.enabled = true; - } - RedrawEvent::Flush => { - trace!("Image flushed"); - self.send_cursor_info(); - self.draw_command_batcher.send_batch().ok(); - REDRAW_SCHEDULER.queue_next_frame(); - } - RedrawEvent::DefaultColorsSet { colors } => { - self.draw_command_batcher - .queue(DrawCommand::DefaultStyleChanged(Style::new(colors))) - .ok(); - } - RedrawEvent::HighlightAttributesDefine { id, style } => { - self.defined_styles.insert(id, Arc::new(style)); - } - RedrawEvent::CursorGoto { - grid, - column: left, - row: top, - } => self.set_cursor_position(grid, left, top), - RedrawEvent::Resize { - grid, - width, - height, - } => { - self.resize_window(grid, width, height); - } - RedrawEvent::GridLine { - grid, - row, - column_start, - cells, - } => { - let defined_styles = &self.defined_styles; - let window = self.windows.get_mut(&grid); - if let Some(window) = window { - window.draw_grid_line(row, column_start, cells, defined_styles); + RedrawEvent::ModeInfoSet { cursor_modes } => self.mode_list = cursor_modes, + RedrawEvent::OptionSet { gui_option } => self.set_option(gui_option), + RedrawEvent::ModeChange { mode, mode_index } => { + if let Some(cursor_mode) = self.mode_list.get(mode_index as usize) { + self.cursor.change_mode(cursor_mode, &self.defined_styles); + } + self.draw_command_batcher + .queue(DrawCommand::ModeChanged(mode)) + .ok(); } - } - RedrawEvent::Clear { grid } => { - let window = self.windows.get_mut(&grid); - if let Some(window) = window { - window.clear(); + RedrawEvent::MouseOn => { + EVENT_AGGREGATOR.send(WindowCommand::SetMouseEnabled(true)); } - } - RedrawEvent::Destroy { grid } => self.close_window(grid), - RedrawEvent::Scroll { - grid, - top, - bottom, - left, - right, - rows, - columns, - } => { - let window = self.windows.get_mut(&grid); - if let Some(window) = window { - window.scroll_region(top, bottom, left, right, rows, columns); + RedrawEvent::MouseOff => { + EVENT_AGGREGATOR.send(WindowCommand::SetMouseEnabled(false)); } - } - RedrawEvent::WindowPosition { - grid, - start_row, - start_column, - width, - height, - } => self.set_window_position(grid, start_column, start_row, width, height), - RedrawEvent::WindowFloatPosition { - grid, - anchor, - anchor_grid, - anchor_column: anchor_left, - anchor_row: anchor_top, - sort_order, - .. - } => self.set_window_float_position( - grid, - anchor_grid, - anchor, - anchor_left, - anchor_top, - sort_order, - ), - RedrawEvent::WindowHide { grid } => { - let window = self.windows.get(&grid); - if let Some(window) = window { - window.hide(); + RedrawEvent::BusyStart => { + trace!("Cursor off"); + self.cursor.enabled = false; } - } - RedrawEvent::WindowClose { grid } => self.close_window(grid), - RedrawEvent::MessageSetPosition { grid, row, .. } => { - self.set_message_position(grid, row) - } - RedrawEvent::WindowViewport { - grid, - top_line, - bottom_line, - .. - } => self.send_updated_viewport(grid, top_line, bottom_line), - _ => {} + RedrawEvent::BusyStop => { + trace!("Cursor on"); + self.cursor.enabled = true; + } + RedrawEvent::Flush => { + trace!("Image flushed"); + self.send_cursor_info(); + self.draw_command_batcher.send_batch(); + REDRAW_SCHEDULER.queue_next_frame(); + } + RedrawEvent::DefaultColorsSet { colors } => { + self.draw_command_batcher + .queue(DrawCommand::DefaultStyleChanged(Style::new(colors))) + .ok(); + } + RedrawEvent::HighlightAttributesDefine { id, style } => { + self.defined_styles.insert(id, Arc::new(style)); + } + RedrawEvent::CursorGoto { + grid, + column: left, + row: top, + } => self.set_cursor_position(grid, left, top), + RedrawEvent::Resize { + grid, + width, + height, + } => { + self.resize_window(grid, width, height); + } + RedrawEvent::GridLine { + grid, + row, + column_start, + cells, + } => { + let defined_styles = &self.defined_styles; + let window = self.windows.get_mut(&grid); + if let Some(window) = window { + window.draw_grid_line(row, column_start, cells, defined_styles); + } + } + RedrawEvent::Clear { grid } => { + let window = self.windows.get_mut(&grid); + if let Some(window) = window { + window.clear(); + } + } + RedrawEvent::Destroy { grid } => self.close_window(grid), + RedrawEvent::Scroll { + grid, + top, + bottom, + left, + right, + rows, + columns, + } => { + let window = self.windows.get_mut(&grid); + if let Some(window) = window { + window.scroll_region(top, bottom, left, right, rows, columns); + } + } + RedrawEvent::WindowPosition { + grid, + start_row, + start_column, + width, + height, + } => self.set_window_position(grid, start_column, start_row, width, height), + RedrawEvent::WindowFloatPosition { + grid, + anchor, + anchor_grid, + anchor_column: anchor_left, + anchor_row: anchor_top, + sort_order, + .. + } => self.set_window_float_position( + grid, + anchor_grid, + anchor, + anchor_left, + anchor_top, + sort_order, + ), + RedrawEvent::WindowHide { grid } => { + let window = self.windows.get(&grid); + if let Some(window) = window { + window.hide(); + } + } + RedrawEvent::WindowClose { grid } => self.close_window(grid), + RedrawEvent::MessageSetPosition { grid, row, .. } => { + self.set_message_position(grid, row) + } + RedrawEvent::WindowViewport { + grid, + top_line, + bottom_line, + .. + } => self.send_updated_viewport(grid, top_line, bottom_line), + _ => {} + }, + EditorCommand::RedrawScreen => self.redraw_screen(), }; } @@ -423,18 +402,14 @@ impl Editor { trace!("Option set {:?}", &gui_option); if let GuiOption::GuiFont(guifont) = gui_option { if guifont == *"*" { - self.window_command_sender - .send(WindowCommand::ListAvailableFonts) - .ok(); + EVENT_AGGREGATOR.send(WindowCommand::ListAvailableFonts); } self.draw_command_batcher .queue(DrawCommand::FontChanged(guifont)) .ok(); - for window in self.windows.values() { - window.redraw(); - } + self.redraw_screen(); } } @@ -445,18 +420,21 @@ impl Editor { trace!("viewport event received before window initialized"); } } + + fn redraw_screen(&mut self) { + for window in self.windows.values() { + window.redraw(); + } + } } -pub fn start_editor( - mut redraw_event_receiver: UnboundedReceiver, - batched_draw_command_sender: LoggingSender>, - window_command_sender: LoggingSender, -) { +pub fn start_editor() { thread::spawn(move || { - let mut editor = Editor::new(batched_draw_command_sender, window_command_sender); + let mut editor = Editor::new(); - while let Some(redraw_event) = redraw_event_receiver.blocking_recv() { - editor.handle_redraw_event(redraw_event); + let mut editor_command_receiver = EVENT_AGGREGATOR.register_event::(); + while let Some(editor_command) = editor_command_receiver.blocking_recv() { + editor.handle_editor_command(editor_command); } }); } diff --git a/src/editor/window.rs b/src/editor/window.rs index 1116762..aa02c6f 100644 --- a/src/editor/window.rs +++ b/src/editor/window.rs @@ -8,41 +8,7 @@ use super::grid::CharacterGrid; use super::style::Style; use super::{AnchorInfo, DrawCommand, DrawCommandBatcher}; use crate::bridge::GridLineCell; - -#[derive(Clone, Debug)] -pub struct LineFragment { - pub text: String, - pub window_left: u64, - pub window_top: u64, - pub width: u64, - pub style: Option>, -} - -#[derive(Clone, Debug)] -pub enum WindowDrawCommand { - Position { - grid_position: (f64, f64), - grid_size: (u64, u64), - floating_order: Option, - }, - DrawLine(Vec), - Scroll { - top: u64, - bottom: u64, - left: u64, - right: u64, - rows: i64, - cols: i64, - }, - Clear, - Show, - Hide, - Close, - Viewport { - top_line: f64, - bottom_line: f64, - }, -} +use crate::renderer::{LineFragment, WindowDrawCommand}; pub enum WindowType { Editor, diff --git a/src/event_aggregator.rs b/src/event_aggregator.rs new file mode 100644 index 0000000..3db9c45 --- /dev/null +++ b/src/event_aggregator.rs @@ -0,0 +1,75 @@ +use std::any::{type_name, Any, TypeId}; +use std::collections::{hash_map::Entry, HashMap}; +use std::fmt::Debug; + +use parking_lot::{Mutex, RwLock}; +use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; + +use crate::channel_utils::*; + +lazy_static! { + pub static ref EVENT_AGGREGATOR: EventAggregator = EventAggregator::default(); +} + +thread_local! { + static THREAD_SENDERS: RwLock>> = RwLock::new(HashMap::new()); +} + +pub struct EventAggregator { + parent_senders: RwLock>>>, + unclaimed_receivers: RwLock>>, +} + +impl Default for EventAggregator { + fn default() -> Self { + EventAggregator { + parent_senders: RwLock::new(HashMap::new()), + unclaimed_receivers: RwLock::new(HashMap::new()), + } + } +} + +impl EventAggregator { + fn get_sender(&self) -> LoggingTx { + match self.parent_senders.write().entry(TypeId::of::()) { + Entry::Occupied(entry) => { + let sender = entry.get().lock(); + sender.downcast_ref::>().unwrap().clone() + } + Entry::Vacant(entry) => { + let (sender, receiver) = unbounded_channel(); + let logging_tx = LoggingTx::attach(sender, type_name::().to_owned()); + entry.insert(Mutex::new(Box::new(logging_tx.clone()))); + self.unclaimed_receivers + .write() + .insert(TypeId::of::(), Box::new(receiver)); + logging_tx + } + } + } + + pub fn send(&self, event: T) { + let sender = self.get_sender::(); + sender.send(event).unwrap(); + } + + pub fn register_event(&self) -> UnboundedReceiver { + let type_id = TypeId::of::(); + + if let Some(receiver) = self.unclaimed_receivers.write().remove(&type_id) { + *receiver.downcast::>().unwrap() + } else { + let (sender, receiver) = unbounded_channel(); + let logging_sender = LoggingTx::attach(sender, type_name::().to_owned()); + + match self.parent_senders.write().entry(type_id) { + Entry::Occupied(_) => panic!("EventAggregator: type already registered"), + Entry::Vacant(entry) => { + entry.insert(Mutex::new(Box::new(logging_sender))); + } + } + + receiver + } + } +} diff --git a/src/main.rs b/src/main.rs index a061607..50562ec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ mod channel_utils; mod cmd_line; mod editor; mod error_handling; +mod event_aggregator; mod redraw_scheduler; mod renderer; mod running_tracker; @@ -31,10 +32,8 @@ extern crate derive_new; extern crate lazy_static; use std::env::args; -use std::sync::mpsc::channel; use log::trace; -use tokio::sync::mpsc::unbounded_channel; use bridge::start_bridge; use cmd_line::CmdLineSettings; @@ -44,6 +43,7 @@ use settings::SETTINGS; use window::{create_window, KeyboardSettings, WindowSettings}; pub use channel_utils::*; +pub use event_aggregator::*; pub use running_tracker::*; pub use windows_utils::*; @@ -144,40 +144,10 @@ fn main() { CursorSettings::register(); KeyboardSettings::register(); - let (redraw_event_sender, redraw_event_receiver) = unbounded_channel(); - let logging_redraw_event_sender = - LoggingTx::attach(redraw_event_sender, "redraw_event".to_owned()); - - let (batched_draw_command_sender, batched_draw_command_receiver) = channel(); - let logging_batched_draw_command_sender = LoggingSender::attach( - batched_draw_command_sender, - "batched_draw_command".to_owned(), - ); - - let (ui_command_sender, ui_command_receiver) = unbounded_channel(); - let logging_ui_command_sender = LoggingTx::attach(ui_command_sender, "ui_command".to_owned()); - - let (window_command_sender, window_command_receiver) = channel(); - let logging_window_command_sender = - LoggingSender::attach(window_command_sender, "window_command".to_owned()); - // We need to keep the bridge reference around to prevent the tokio runtime from getting freed - let _bridge = start_bridge( - #[cfg(windows)] - logging_ui_command_sender.clone(), - ui_command_receiver, - logging_redraw_event_sender, - ); - start_editor( - redraw_event_receiver, - logging_batched_draw_command_sender, - logging_window_command_sender, - ); - create_window( - batched_draw_command_receiver, - window_command_receiver, - logging_ui_command_sender, - ); + let _bridge = start_bridge(); + start_editor(); + create_window(); } #[cfg(not(test))] diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 90736cd..71f608e 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -7,19 +7,20 @@ mod rendered_window; use crate::WindowSettings; use std::cmp::Ordering; use std::collections::{hash_map::Entry, HashMap}; -use std::sync::mpsc::Receiver; use std::sync::Arc; use log::error; use skia_safe::Canvas; +use tokio::sync::mpsc::UnboundedReceiver; use crate::bridge::EditorMode; -use crate::editor::{DrawCommand, WindowDrawCommand}; +use crate::editor::{Cursor, Style}; +use crate::event_aggregator::EVENT_AGGREGATOR; use crate::settings::*; use cursor_renderer::CursorRenderer; pub use fonts::caching_shaper::CachingShaper; pub use grid_renderer::GridRenderer; -pub use rendered_window::{RenderedWindow, WindowDrawDetails}; +pub use rendered_window::{LineFragment, RenderedWindow, WindowDrawCommand, WindowDrawDetails}; #[derive(SettingGroup, Clone)] pub struct RendererSettings { @@ -42,6 +43,19 @@ impl Default for RendererSettings { } } +#[derive(Clone, Debug)] +pub enum DrawCommand { + CloseWindow(u64), + Window { + grid_id: u64, + command: WindowDrawCommand, + }, + UpdateCursor(Cursor), + FontChanged(String), + DefaultStyleChanged(Style), + ModeChanged(EditorMode), +} + pub struct Renderer { cursor_renderer: CursorRenderer, pub grid_renderer: GridRenderer, @@ -50,14 +64,11 @@ pub struct Renderer { rendered_windows: HashMap, pub window_regions: Vec, - pub batched_draw_command_receiver: Receiver>, + pub batched_draw_command_receiver: UnboundedReceiver>, } impl Renderer { - pub fn new( - batched_draw_command_receiver: Receiver>, - scale_factor: f64, - ) -> Self { + pub fn new(scale_factor: f64) -> Self { let cursor_renderer = CursorRenderer::new(); let grid_renderer = GridRenderer::new(scale_factor); let current_mode = EditorMode::Unknown(String::from("")); @@ -65,6 +76,8 @@ impl Renderer { let rendered_windows = HashMap::new(); let window_regions = Vec::new(); + let batched_draw_command_receiver = EVENT_AGGREGATOR.register_event::>(); + Renderer { rendered_windows, cursor_renderer, @@ -85,12 +98,11 @@ impl Renderer { /// `bool` indicating whether or not font was changed during this frame. #[allow(clippy::needless_collect)] pub fn draw_frame(&mut self, root_canvas: &mut Canvas, dt: f32) -> bool { - let draw_commands: Vec<_> = self - .batched_draw_command_receiver - .try_iter() // Iterator of Vec of DrawCommand - .map(|batch| batch.into_iter()) // Iterator of Iterator of DrawCommand - .flatten() // Iterator of DrawCommand - .collect(); + let mut draw_commands = Vec::new(); + while let Ok(draw_command) = self.batched_draw_command_receiver.try_recv() { + draw_commands.extend(draw_command); + } + let mut font_changed = false; for draw_command in draw_commands.into_iter() { diff --git a/src/renderer/rendered_window.rs b/src/renderer/rendered_window.rs index a6a1065..cc68aa0 100644 --- a/src/renderer/rendered_window.rs +++ b/src/renderer/rendered_window.rs @@ -1,4 +1,5 @@ use std::collections::VecDeque; +use std::sync::Arc; use skia_safe::canvas::{SaveLayerRec, SrcRectConstraint}; use skia_safe::gpu::SurfaceOrigin; @@ -9,10 +10,45 @@ use skia_safe::{ use super::animation_utils::*; use super::{GridRenderer, RendererSettings}; -use crate::editor::{LineFragment, WindowDrawCommand}; +use crate::editor::Style; use crate::redraw_scheduler::REDRAW_SCHEDULER; use crate::utils::Dimensions; +#[derive(Clone, Debug)] +pub struct LineFragment { + pub text: String, + pub window_left: u64, + pub window_top: u64, + pub width: u64, + pub style: Option>, +} + +#[derive(Clone, Debug)] +pub enum WindowDrawCommand { + Position { + grid_position: (f64, f64), + grid_size: (u64, u64), + floating_order: Option, + }, + DrawLine(Vec), + Scroll { + top: u64, + bottom: u64, + left: u64, + right: u64, + rows: i64, + cols: i64, + }, + Clear, + Show, + Hide, + Close, + Viewport { + top_line: f64, + bottom_line: f64, + }, +} + fn build_window_surface(parent_canvas: &mut Canvas, pixel_size: (i32, i32)) -> Surface { let mut context = parent_canvas.recording_context().unwrap(); let budgeted = Budgeted::Yes; diff --git a/src/window/keyboard_manager.rs b/src/window/keyboard_manager.rs index b2a9c6e..d8ae5c1 100644 --- a/src/window/keyboard_manager.rs +++ b/src/window/keyboard_manager.rs @@ -4,12 +4,11 @@ use glutin::keyboard::Key; use glutin::platform::modifier_supplement::KeyEventExtModifierSupplement; use crate::bridge::{SerialCommand, UiCommand}; -use crate::channel_utils::LoggingTx; +use crate::event_aggregator::EVENT_AGGREGATOR; use crate::settings::SETTINGS; use crate::window::KeyboardSettings; pub struct KeyboardManager { - command_sender: LoggingTx, shift: bool, ctrl: bool, alt: bool, @@ -19,9 +18,8 @@ pub struct KeyboardManager { } impl KeyboardManager { - pub fn new(command_sender: LoggingTx) -> KeyboardManager { + pub fn new() -> KeyboardManager { KeyboardManager { - command_sender, shift: false, ctrl: false, alt: false, @@ -73,9 +71,8 @@ impl KeyboardManager { // And a key was pressed if key_event.state == ElementState::Pressed { if let Some(keybinding) = self.maybe_get_keybinding(key_event) { - self.command_sender - .send(SerialCommand::Keyboard(keybinding).into()) - .expect("Could not send keyboard ui command"); + EVENT_AGGREGATOR + .send(UiCommand::Serial(SerialCommand::Keyboard(keybinding))); } } } diff --git a/src/window/mod.rs b/src/window/mod.rs index 9489d2a..91e5ad8 100644 --- a/src/window/mod.rs +++ b/src/window/mod.rs @@ -3,10 +3,7 @@ mod mouse_manager; mod renderer; mod settings; -use std::{ - sync::mpsc::Receiver, - time::{Duration, Instant}, -}; +use std::time::{Duration, Instant}; use glutin::{ self, @@ -17,16 +14,21 @@ use glutin::{ ContextBuilder, GlProfile, WindowedContext, }; use log::trace; +use tokio::sync::mpsc::UnboundedReceiver; #[cfg(target_os = "linux")] use glutin::platform::unix::WindowBuilderExtUnix; +use image::{load_from_memory, GenericImageView, Pixel}; +use keyboard_manager::KeyboardManager; +use mouse_manager::MouseManager; +use renderer::SkiaRenderer; + use crate::{ bridge::{ParallelCommand, UiCommand}, - channel_utils::*, cmd_line::CmdLineSettings, - editor::DrawCommand, - editor::WindowCommand, + editor::EditorCommand, + event_aggregator::EVENT_AGGREGATOR, redraw_scheduler::REDRAW_SCHEDULER, renderer::Renderer, running_tracker::*, @@ -35,11 +37,6 @@ use crate::{ }, utils::Dimensions, }; -use image::{load_from_memory, GenericImageView, Pixel}; -use keyboard_manager::KeyboardManager; -use mouse_manager::MouseManager; -use renderer::SkiaRenderer; - pub use settings::{KeyboardSettings, WindowSettings}; static ICON: &[u8] = include_bytes!("../../assets/neovide.ico"); @@ -47,6 +44,13 @@ static ICON: &[u8] = include_bytes!("../../assets/neovide.ico"); const MIN_WINDOW_WIDTH: u64 = 20; const MIN_WINDOW_HEIGHT: u64 = 6; +#[derive(Clone, Debug)] +pub enum WindowCommand { + TitleChanged(String), + SetMouseEnabled(bool), + ListAvailableFonts, +} + pub struct GlutinWindowWrapper { windowed_context: WindowedContext, skia_renderer: SkiaRenderer, @@ -57,8 +61,7 @@ pub struct GlutinWindowWrapper { fullscreen: bool, saved_inner_size: PhysicalSize, saved_grid_size: Option, - ui_command_sender: LoggingTx, - window_command_receiver: Receiver, + window_command_receiver: UnboundedReceiver, } impl GlutinWindowWrapper { @@ -84,8 +87,7 @@ impl GlutinWindowWrapper { #[allow(clippy::needless_collect)] pub fn handle_window_commands(&mut self) { - let window_commands: Vec = self.window_command_receiver.try_iter().collect(); - for window_command in window_commands.into_iter() { + while let Ok(window_command) = self.window_command_receiver.try_recv() { match window_command { WindowCommand::TitleChanged(new_title) => self.handle_title_changed(new_title), WindowCommand::SetMouseEnabled(mouse_enabled) => { @@ -103,33 +105,25 @@ impl GlutinWindowWrapper { pub fn send_font_names(&self) { let font_names = self.renderer.font_names(); - self.ui_command_sender - .send(UiCommand::Parallel(ParallelCommand::DisplayAvailableFonts( - font_names, - ))) - .unwrap(); + EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::DisplayAvailableFonts( + font_names, + ))); } pub fn handle_quit(&mut self) { if SETTINGS.get::().remote_tcp.is_none() { - self.ui_command_sender - .send(ParallelCommand::Quit.into()) - .expect("Could not send quit command to bridge"); + EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::Quit)); } else { RUNNING_TRACKER.quit("window closed"); } } pub fn handle_focus_lost(&mut self) { - self.ui_command_sender - .send(ParallelCommand::FocusLost.into()) - .ok(); + EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::FocusLost)); } pub fn handle_focus_gained(&mut self) { - self.ui_command_sender - .send(ParallelCommand::FocusGained.into()) - .ok(); + EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::FocusGained)); REDRAW_SCHEDULER.queue_next_frame(); } @@ -145,6 +139,9 @@ impl GlutinWindowWrapper { Event::LoopDestroyed => { self.handle_quit(); } + Event::Resumed => { + EVENT_AGGREGATOR.send(EditorCommand::RedrawScreen); + } Event::WindowEvent { event: WindowEvent::CloseRequested, .. @@ -161,12 +158,8 @@ impl GlutinWindowWrapper { event: WindowEvent::DroppedFile(path), .. } => { - self.ui_command_sender - .send( - ParallelCommand::FileDrop(path.into_os_string().into_string().unwrap()) - .into(), - ) - .ok(); + let file_path = path.into_os_string().into_string().unwrap(); + EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::FileDrop(file_path))); } Event::WindowEvent { event: WindowEvent::Focused(focus), @@ -241,15 +234,10 @@ impl GlutinWindowWrapper { return; } self.saved_grid_size = Some(grid_size); - self.ui_command_sender - .send( - ParallelCommand::Resize { - width: grid_size.width, - height: grid_size.height, - } - .into(), - ) - .ok(); + EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::Resize { + width: grid_size.width, + height: grid_size.height, + })); } fn handle_scale_factor_update(&mut self, scale_factor: f64) { @@ -259,11 +247,7 @@ impl GlutinWindowWrapper { } } -pub fn create_window( - batched_draw_command_receiver: Receiver>, - window_command_receiver: Receiver, - ui_command_sender: LoggingTx, -) { +pub fn create_window() { let icon = { let icon = load_from_memory(ICON).expect("Failed to parse icon data"); let (width, height) = icon.dimensions(); @@ -322,11 +306,13 @@ pub fn create_window( let window = windowed_context.window(); let scale_factor = windowed_context.window().scale_factor(); - let renderer = Renderer::new(batched_draw_command_receiver, scale_factor); + let renderer = Renderer::new(scale_factor); let saved_inner_size = window.inner_size(); let skia_renderer = SkiaRenderer::new(&windowed_context); + let window_command_receiver = EVENT_AGGREGATOR.register_event::(); + log::info!( "window created (scale_factor: {:.4}, font_dimensions: {:?})", scale_factor, @@ -337,13 +323,12 @@ pub fn create_window( windowed_context, skia_renderer, renderer, - keyboard_manager: KeyboardManager::new(ui_command_sender.clone()), - mouse_manager: MouseManager::new(ui_command_sender.clone()), + keyboard_manager: KeyboardManager::new(), + mouse_manager: MouseManager::new(), title: String::from("Neovide"), fullscreen: false, saved_inner_size, saved_grid_size: None, - ui_command_sender, window_command_receiver, }; diff --git a/src/window/mouse_manager.rs b/src/window/mouse_manager.rs index 265145f..6545fac 100644 --- a/src/window/mouse_manager.rs +++ b/src/window/mouse_manager.rs @@ -10,7 +10,7 @@ use skia_safe::Rect; use super::keyboard_manager::KeyboardManager; use crate::bridge::{SerialCommand, UiCommand}; -use crate::channel_utils::LoggingTx; +use crate::event_aggregator::EVENT_AGGREGATOR; use crate::renderer::{Renderer, WindowDrawDetails}; use crate::settings::SETTINGS; use crate::window::WindowSettings; @@ -52,8 +52,6 @@ fn mouse_button_to_button_text(mouse_button: &MouseButton) -> Option { } pub struct MouseManager { - command_sender: LoggingTx, - dragging: Option, drag_position: PhysicalPosition, @@ -70,9 +68,8 @@ pub struct MouseManager { } impl MouseManager { - pub fn new(command_sender: LoggingTx) -> MouseManager { + pub fn new() -> MouseManager { MouseManager { - command_sender, dragging: None, has_moved: false, position: PhysicalPosition::new(0, 0), @@ -166,17 +163,12 @@ impl MouseManager { // If dragging and we haven't already sent a position, send a drag command if self.dragging.is_some() && has_moved { - self.command_sender - .send( - SerialCommand::Drag { - button: self.dragging.as_ref().unwrap().to_owned(), - grid_id: relevant_window_details.id, - position: self.drag_position.into(), - modifier_string: keyboard_manager.format_modifier_string(true), - } - .into(), - ) - .ok(); + EVENT_AGGREGATOR.send(UiCommand::Serial(SerialCommand::Drag { + button: self.dragging.as_ref().unwrap().to_owned(), + grid_id: relevant_window_details.id, + position: self.drag_position.into(), + modifier_string: keyboard_manager.format_modifier_string(true), + })); } else { // otherwise, update the window_id_under_mouse to match the one selected self.window_details_under_mouse = Some(relevant_window_details.clone()); @@ -210,18 +202,13 @@ impl MouseManager { self.relative_position }; - self.command_sender - .send( - SerialCommand::MouseButton { - button: button_text.clone(), - action, - grid_id: details.id, - position: position.into(), - modifier_string: keyboard_manager.format_modifier_string(true), - } - .into(), - ) - .ok(); + EVENT_AGGREGATOR.send(UiCommand::Serial(SerialCommand::MouseButton { + button: button_text.clone(), + action, + grid_id: details.id, + position: position.into(), + modifier_string: keyboard_manager.format_modifier_string(true), + })); } if down { @@ -265,7 +252,7 @@ impl MouseManager { } .into(); for _ in 0..(new_y - previous_y).abs() { - self.command_sender.send(scroll_command.clone()).ok(); + EVENT_AGGREGATOR.send(scroll_command.clone()); } } @@ -292,7 +279,7 @@ impl MouseManager { } .into(); for _ in 0..(new_x - previous_x).abs() { - self.command_sender.send(scroll_command.clone()).ok(); + EVENT_AGGREGATOR.send(scroll_command.clone()); } } }