diff --git a/Cargo.lock b/Cargo.lock index 1966f79..2827128 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -415,6 +415,17 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -467,6 +478,12 @@ dependencies = [ "syn", ] +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + [[package]] name = "futures-task" version = "0.3.30" @@ -1287,6 +1304,15 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "stability" version = "0.2.0" @@ -1362,6 +1388,7 @@ dependencies = [ "cairo-rs", "criterion", "crossterm", + "flume", "futures-util", "glib", "image", diff --git a/Cargo.toml b/Cargo.toml index b2f0884..6fa2be3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,10 +24,11 @@ ratatui-image = { git = "https://github.com/itsjunetime/ratatui-image.git", bran crossterm = { version = "0.27.0", features = ["event-stream"] } image = { version = "0.25.1", features = ["png", "rayon"], default-features = false } notify = "6.1.1" -tokio = { version = "1.37.0", features = ["rt-multi-thread", "sync", "macros"] } +tokio = { version = "1.37.0", features = ["rt-multi-thread", "macros"] } futures-util = { version = "0.3.30", default-features = false } glib = "0.19.6" itertools = "*" +flume = { version = "0.11.0", default-features = false, features = ["async"] } [profile.production] inherits = "release" diff --git a/benches/rendering.rs b/benches/rendering.rs index b85db2a..df1352a 100644 --- a/benches/rendering.rs +++ b/benches/rendering.rs @@ -26,5 +26,9 @@ fn render_example(c: &mut Criterion) { ); } -criterion_group!(benches, render_dict, render_example); +criterion_group!( + name = benches; + config = Criterion::default().sample_size(10); + targets = render_dict, render_example +); criterion_main!(benches); diff --git a/benches/utils.rs b/benches/utils.rs index 533fb39..4ce6a19 100644 --- a/benches/utils.rs +++ b/benches/utils.rs @@ -1,17 +1,18 @@ use std::{hint::black_box, path::Path}; use crossterm::terminal::WindowSize; +use flume::{unbounded, Sender}; use ratatui::layout::Rect; use ratatui_image::picker::{Picker, ProtocolType}; use tdf::{converter::{run_conversion_loop, ConvertedPage, ConverterMsg}, renderer::{fill_default, start_rendering, RenderError, RenderInfo, RenderNotif}}; -use tokio::sync::mpsc::{unbounded_channel, UnboundedSender}; +use futures_util::stream::StreamExt as _; pub async fn render_doc(path: impl AsRef) { let pathbuf = path.as_ref().canonicalize().unwrap(); let str_path = format!("file://{}", pathbuf.into_os_string().to_string_lossy()); - let (to_render_tx, from_main_rx) = unbounded_channel(); - let (to_main_tx, mut from_render_rx) = unbounded_channel(); + let (to_render_tx, from_main_rx) = unbounded(); + let (to_main_tx, from_render_rx) = unbounded(); let font_size = (8, 14); let (columns, rows) = (60, 180); @@ -27,8 +28,8 @@ pub async fn render_doc(path: impl AsRef) { start_rendering(str_path, to_main_tx, from_main_rx, size) }); - let (mut to_converter_tx, from_main_rx) = unbounded_channel(); - let (to_main_tx, mut from_converter_rx) = unbounded_channel(); + let (mut to_converter_tx, from_main_rx) = unbounded(); + let (to_main_tx, from_converter_rx) = unbounded(); let mut picker = Picker::new(font_size); picker.protocol_type = ProtocolType::Kitty; @@ -40,7 +41,7 @@ pub async fn render_doc(path: impl AsRef) { fn handle_renderer_msg( msg: Result, pages: &mut Vec>, - to_converter_tx: &mut UnboundedSender, + to_converter_tx: &mut Sender, ) { match msg { Ok(RenderInfo::NumPages(num)) => { @@ -55,7 +56,7 @@ pub async fn render_doc(path: impl AsRef) { fn handle_converter_msg( msg: Result, pages: &mut [Option], - to_converter_tx: &mut UnboundedSender + to_converter_tx: &mut Sender ) { let page = msg.expect("Got error from converter"); let num = page.num; @@ -79,12 +80,15 @@ pub async fn render_doc(path: impl AsRef) { }; to_render_tx.send(RenderNotif::Area(main_area)).unwrap(); + let mut from_render_rx = from_render_rx.into_stream(); + let mut from_converter_rx = from_converter_rx.into_stream(); + while pages.is_empty() || pages.iter().any(|p| p.is_none()) { tokio::select! { - Some(renderer_msg) = from_render_rx.recv() => { + Some(renderer_msg) = from_render_rx.next() => { handle_renderer_msg(renderer_msg, &mut pages, &mut to_converter_tx); }, - Some(converter_msg) = from_converter_rx.recv() => { + Some(converter_msg) = from_converter_rx.next() => { handle_converter_msg(converter_msg, &mut pages, &mut to_converter_tx); } } diff --git a/src/converter.rs b/src/converter.rs index fd842fd..78a3832 100644 --- a/src/converter.rs +++ b/src/converter.rs @@ -1,10 +1,8 @@ +use flume::{Receiver, SendError, Sender, TryRecvError}; use image::ImageFormat; use itertools::Itertools; use ratatui_image::{picker::Picker, protocol::Protocol, Resize}; -use tokio::sync::mpsc::{ - error::{SendError, TryRecvError}, - UnboundedReceiver, UnboundedSender -}; +use futures_util::stream::StreamExt; use crate::renderer::{fill_default, PageInfo, RenderError}; @@ -23,8 +21,8 @@ pub enum ConverterMsg { } pub async fn run_conversion_loop( - sender: UnboundedSender>, - mut receiver: UnboundedReceiver, + sender: Sender>, + receiver: Receiver, mut picker: Picker ) -> Result<(), SendError>> { let mut images = vec![]; @@ -119,7 +117,7 @@ pub async fn run_conversion_loop( } } - let Some(msg) = receiver.recv().await else { + let Some(msg) = receiver.stream().next().await else { break; }; diff --git a/src/main.rs b/src/main.rs index 60e346d..3918513 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,7 +44,8 @@ async fn main() -> Result<(), Box> { let file = std::env::args().nth(1).ok_or("Program requires a file to process")?; let path = PathBuf::from_str(&file)?.canonicalize()?; - let (watch_tx, render_rx) = tokio::sync::mpsc::unbounded_channel(); + //let (watch_tx, render_rx) = tokio::sync::mpsc::unbounded_channel(); + let (watch_tx, render_rx) = flume::unbounded(); let tui_tx = watch_tx.clone(); // we need to call this outside the recommended_watcher call because if we call it inside, that @@ -62,7 +63,7 @@ async fn main() -> Result<(), Box> { watcher.watch(&path, RecursiveMode::NonRecursive)?; let file_path = format!("file://{}", path.clone().into_os_string().to_string_lossy()); - let (render_tx, mut tui_rx) = tokio::sync::mpsc::unbounded_channel(); + let (render_tx, tui_rx) = flume::unbounded(); let mut window_size = window_size()?; @@ -132,8 +133,8 @@ async fn main() -> Result<(), Box> { let mut ev_stream = crossterm::event::EventStream::new(); - let (to_converter, from_main) = tokio::sync::mpsc::unbounded_channel(); - let (to_main, mut from_converter) = tokio::sync::mpsc::unbounded_channel(); + let (to_converter, from_main) = flume::unbounded(); + let (to_main, from_converter) = flume::unbounded(); tokio::spawn(run_conversion_loop(to_main, from_main, picker)); @@ -163,6 +164,9 @@ async fn main() -> Result<(), Box> { let mut main_area = tui::Tui::main_layout(&term.get_frame()); tui_tx.send(RenderNotif::Area(main_area[1]))?; + let mut tui_rx = tui_rx.into_stream(); + let mut from_converter = from_converter.into_stream(); + loop { let mut needs_redraw = tokio::select! { // First we check if we have any keystrokes @@ -186,7 +190,7 @@ async fn main() -> Result<(), Box> { } } }, - Some(renderer_msg) = tui_rx.recv() => { + Some(renderer_msg) = tui_rx.next() => { match renderer_msg { Ok(RenderInfo::NumPages(num)) => { tui.set_n_pages(num); @@ -200,7 +204,7 @@ async fn main() -> Result<(), Box> { } true } - Some(img_res) = from_converter.recv() => { + Some(img_res) = from_converter.next() => { match img_res { Ok(ConvertedPage { page, num, num_results }) => tui.page_ready(page, num, num_results), Err(e) => tui.show_error(e), diff --git a/src/renderer.rs b/src/renderer.rs index c83b7df..2c4ac2d 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,9 +1,9 @@ use cairo::{Antialias, Format}; use crossterm::terminal::WindowSize; +use flume::{Receiver, Sender, TryRecvError}; use itertools::Itertools; use poppler::{Color, Document, FindFlags, Page, Rectangle, SelectionStyle}; use ratatui::layout::Rect; -use tokio::sync::mpsc::{error::TryRecvError, UnboundedReceiver, UnboundedSender}; pub enum RenderNotif { Area(Rect), @@ -61,15 +61,15 @@ pub fn fill_default(vec: &mut Vec, size: usize) { // we're done. pub fn start_rendering( path: String, - sender: UnboundedSender>, - mut receiver: UnboundedReceiver, + sender: Sender>, + receiver: Receiver, size: WindowSize ) { // first, wait 'til we get told what the current starting area is so that we can set it to // know what to render to let mut area; loop { - if let RenderNotif::Area(r) = receiver.blocking_recv().unwrap() { + if let RenderNotif::Area(r) = receiver.recv().unwrap() { area = r; break; } @@ -231,7 +231,7 @@ pub fn start_rendering( loop { // This once returned None despite the main thing being still connected (I think, at // last), so I'm just being safe here - let Some(msg) = receiver.blocking_recv() else { + let Ok(msg) = receiver.recv() else { return; }; handle_notif!(msg); @@ -293,7 +293,7 @@ fn render_single_page( let surface_height = p_height * scale_factor; let surface = cairo::ImageSurface::create( - Format::ARgb32, + Format::Rgb16_565, // No matter how big you make these arguments, the image will be drawn at the same // size. So if you make them really big, the image will be drawn on a quarter of it. If // you make them really small, the image will cover more than all of the surface. @@ -306,9 +306,9 @@ fn render_single_page( surface_height as i32 ) .map_err(|e| format!("Couldn't create ImageSurface: {e}"))?; - let ctx = cairo::Context::new(surface).map_err(|e| format!("Couldn't create Context: {e}"))?; + surface.set_device_scale(scale_factor, scale_factor); - ctx.scale(scale_factor, scale_factor); + let ctx = cairo::Context::new(surface).map_err(|e| format!("Couldn't create Context: {e}"))?; // The default background color of PDFs (at least, I think) is white, so we need to set // that as the background color, then paint, then render. @@ -344,9 +344,7 @@ fn render_single_page( } } - ctx.scale(1. / scale_factor, 1. / scale_factor); - - let mut img_data = Vec::new(); + let mut img_data = Vec::with_capacity((surface_height * surface_width) as usize); ctx.target() .write_to_png(&mut img_data) .map_err(|e| format!("Couldn't write surface to png: {e}"))?;