From 620d7e0e1c2b5e4edfad03c024a68f12f35f9fc7 Mon Sep 17 00:00:00 2001 From: broccolingual Date: Wed, 11 Mar 2026 06:21:44 +0000 Subject: [PATCH 1/3] refactor: simplify code by removing unused methods and adjusting visibility --- src/attr.rs | 21 --------------------- src/color.rs | 19 ------------------- src/framebuffer.rs | 16 ++++++++-------- src/lib.rs | 20 +++++++++----------- src/macros.rs | 16 ---------------- src/render.rs | 13 +++++-------- src/term.rs | 46 +++++++++++++++++++++------------------------- src/window.rs | 8 ++++---- 8 files changed, 47 insertions(+), 112 deletions(-) delete mode 100644 src/macros.rs diff --git a/src/attr.rs b/src/attr.rs index 1bf4aa4..d91cf7b 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -36,15 +36,6 @@ impl Default for Attr { } impl Attr { - /// Convert attributes to ANSI escape codes - /// - /// Returns a string containing the ANSI escape codes for the active attributes. - pub fn to_ansi(&self) -> String { - let mut buf = String::with_capacity(24); - self.write_ansi(&mut buf); - buf - } - /// Write ANSI escape codes directly into an existing buffer, avoiding allocation. pub fn write_ansi(&self, buf: &mut String) { if self.is_empty() { @@ -92,16 +83,4 @@ mod tests { let attr = Attr::default(); assert_eq!(attr, Attr::NORMAL); } - - #[test] - fn test_attr_to_ansi() { - let attr = Attr::BOLD | Attr::UNDERLINE; - assert_eq!(attr.to_ansi(), "\x1B[1;4m"); - - let attr = Attr::empty(); - assert_eq!(attr.to_ansi(), "\x1B[0m"); - - let attr = Attr::all(); - assert_eq!(attr.to_ansi(), "\x1B[0;1;2;3;4;5;6;7;8;9;10m"); - } } diff --git a/src/color.rs b/src/color.rs index b750cf1..6500761 100644 --- a/src/color.rs +++ b/src/color.rs @@ -16,15 +16,6 @@ pub enum Color { } impl Color { - /// Convert the color to an ANSI escape code. - /// - /// Returns an ANSI escape code string for the color. - pub fn to_ansi(&self, fg: bool) -> String { - let mut buf = String::with_capacity(20); - self.write_ansi(fg, &mut buf); - buf - } - /// Write ANSI escape code directly into an existing buffer, avoiding allocation. pub fn write_ansi(&self, fg: bool, buf: &mut String) { use std::fmt::Write; @@ -77,14 +68,4 @@ mod tests { let color = Color::default(); assert_eq!(color, Color::None); } - - #[test] - fn test_color_to_ansi() { - assert!(Color::Black.to_ansi(true).contains("30m")); - assert!(Color::Red.to_ansi(false).contains("41m")); - assert!(Color::RGB(255, 0, 0).to_ansi(true).contains("38;2;255;0;0")); - assert!(Color::HSV(0, 255, 255) - .to_ansi(true) - .contains("38;2;255;0;0")); - } } diff --git a/src/framebuffer.rs b/src/framebuffer.rs index b183a2e..88e2890 100644 --- a/src/framebuffer.rs +++ b/src/framebuffer.rs @@ -3,26 +3,26 @@ use std::io::{self, Write}; use crate::{Attr, Color}; -const CHUNK_SIZE: usize = 1024; +const CHUNK_SIZE: usize = 256; /// Represents a single cell in the framebuffer. #[derive(Clone, Copy, PartialEq, Debug)] struct Cell { /// The character displayed in the cell. - pub ch: char, + ch: char, /// Text attributes (bold, italic, underline, etc.) - pub attrs: Attr, + attrs: Attr, /// Foreground color as RGB values (0-255 each) - pub fg: Color, + fg: Color, /// Background color as RGB values (0-255 each) - pub bg: Color, + bg: Color, } impl Cell { /// Create a new cell with default values. /// /// Returns a `Cell` instance with default attributes and colors. - pub fn new() -> Self { + fn new() -> Self { Self { ch: ' ', attrs: Attr::default(), @@ -52,7 +52,7 @@ pub struct Framebuffer { pub width: usize, /// The height of the framebuffer. pub height: usize, - buffer: Vec, + buffer: Box<[Cell]>, } impl Framebuffer { @@ -63,7 +63,7 @@ impl Framebuffer { /// /// Returns a new `Framebuffer` instance. pub fn new(width: usize, height: usize) -> Self { - let buffer = vec![Cell::default(); width * height]; + let buffer = vec![Cell::default(); width * height].into_boxed_slice(); Self { width, height, diff --git a/src/lib.rs b/src/lib.rs index d84cca7..5cb9745 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,10 +18,8 @@ The library is organized into several core modules: - [`framebuffer`] - Character grid for efficient rendering - [`window`] - High-level windowing abstraction with thread management - [`input`] - Non-blocking keyboard and mouse input handling -- [`term`] - Terminal colors, attributes, and ANSI escape sequences - [`attr`] - Text attributes and styling - [`color`] - Color manipulation and conversion utilities -- [`render`] - Rendering utilities and drawing primitives ## Performance @@ -43,22 +41,22 @@ pub mod framebuffer; /// A module for handling user input. #[cfg(target_os = "linux")] pub mod input; -/// A module for a rendering context. -#[cfg(target_os = "linux")] -pub mod render; -/// A module for handling terminal colors and attributes. -#[cfg(target_os = "linux")] -pub mod term; /// A module for handling windowing. #[cfg(target_os = "linux")] pub mod window; -mod macros; +/// A module for a rendering context. +#[cfg(target_os = "linux")] +pub(crate) mod render; +/// A module for handling terminal colors and attributes. +#[cfg(target_os = "linux")] +pub(crate) mod term; pub use attr::*; pub use color::*; pub use framebuffer::*; pub use input::*; -pub use render::*; -pub use term::*; pub use window::*; + +pub(crate) use render::*; +pub(crate) use term::*; diff --git a/src/macros.rs b/src/macros.rs deleted file mode 100644 index ecda57a..0000000 --- a/src/macros.rs +++ /dev/null @@ -1,16 +0,0 @@ -/// Create a CSI (Control Sequence Introducer) escape sequence -#[macro_export] -#[doc(hidden)] -macro_rules! csi { - ($x:expr) => { - String::from("\x1B[") + $x - }; -} - -#[cfg(test)] -mod tests { - #[test] - fn test_csi_macro() { - assert_eq!(csi!("?25h"), "\x1B[?25h"); - } -} diff --git a/src/render.rs b/src/render.rs index f8837b4..c2631ba 100644 --- a/src/render.rs +++ b/src/render.rs @@ -9,7 +9,7 @@ use std::time; use crate::Framebuffer; /// Represents a render thread for rendering frames. -pub struct RenderThread { +pub(crate) struct RenderThread { /// The thread handle for the render thread. handle: Option>, /// The stop signal sender for the render thread. @@ -26,7 +26,7 @@ impl RenderThread { /// * `rendering_rate` - The rate at which to render frames. /// /// Returns `RenderThread` instance. - pub fn new( + pub(crate) fn new( front_fb: Arc>, back_fb: Arc>, rendering_rate: time::Duration, @@ -64,10 +64,7 @@ impl RenderThread { } } Err(TryLockError::WouldBlock) => { - // back_fb is locked by draw(); retry immediately without - // resetting the frame timer so the next iteration does not - // sleep a full rendering_rate before retrying. - continue; + thread::yield_now(); // Yield to other threads if the back framebuffer is currently locked } Err(_) => { break; @@ -98,14 +95,14 @@ impl RenderThread { /// Try to receive the current FPS /// /// Returns the current FPS if available, or an error if not. - pub fn try_recv_fps(&self) -> Result { + pub(crate) fn try_recv_fps(&self) -> Result { self.fps_rx.try_recv() } /// Stop the render thread /// /// Returns `Ok(())` if the thread was stopped successfully, or an error if it failed. - pub fn stop(&mut self) -> Result<(), Box> { + pub(crate) fn stop(&mut self) -> Result<(), Box> { if let Some(tx) = self.stop_signal.take() { tx.send(())?; // Send stop signal } diff --git a/src/term.rs b/src/term.rs index 737bb7e..7648b22 100644 --- a/src/term.rs +++ b/src/term.rs @@ -1,4 +1,3 @@ -use crate::csi; use nix::libc; use nix::sys::termios::{self, ControlFlags, InputFlags, LocalFlags, OutputFlags, SetArg, Termios}; use std::os::unix::io::{BorrowedFd, RawFd}; @@ -11,8 +10,6 @@ use std::{ pub enum Cmd { ShowCursor, HideCursor, - MoveCursor(usize, usize), - MoveCursorToHome, ClearScreen, EnableAlternativeScreen, DisableAlternativeScreen, @@ -23,7 +20,7 @@ pub enum Cmd { } /// Represents a terminal. -pub struct Terminal { +pub(crate) struct Terminal { /// The file descriptor for the terminal. fd: RawFd, /// The original terminal settings. @@ -34,7 +31,7 @@ impl Terminal { /// Create a new terminal instance. /// /// Returns a new `Terminal` instance. - pub fn new() -> Self { + pub(crate) fn new() -> Self { let fd: RawFd = std::io::stdout().as_raw_fd(); Self { fd, original: None } } @@ -52,7 +49,7 @@ impl Terminal { /// Enable raw mode /// /// Returns a `Terminal` instance with raw mode enabled. - pub fn enable_raw_mode(&mut self) -> nix::Result<()> { + pub(crate) fn enable_raw_mode(&mut self) -> nix::Result<()> { let borrowed_fd = self.get_borrowed_fd()?; let original = termios::tcgetattr(borrowed_fd)?; let mut raw = original.clone(); @@ -90,7 +87,7 @@ impl Terminal { /// Disable raw mode /// /// Returns `Ok(())` if successful, or an error if it fails. - pub fn disable_raw_mode(&mut self) -> nix::Result<()> { + pub(crate) fn disable_raw_mode(&mut self) -> nix::Result<()> { if let Some(original) = &self.original { let borrowed_fd = self.get_borrowed_fd()?; termios::tcsetattr(borrowed_fd, SetArg::TCSANOW, original)?; @@ -102,7 +99,7 @@ impl Terminal { /// Set the terminal to non-blocking mode /// /// Returns `Ok(())` if successful, or an error if it fails. - pub fn set_nonblocking(&self) -> nix::Result<()> { + pub(crate) fn set_nonblocking(&self) -> nix::Result<()> { unsafe { let flags = libc::fcntl(self.fd, libc::F_GETFL); if flags == -1 { @@ -121,26 +118,25 @@ impl Terminal { /// * `cmd` - The command to execute. /// /// Returns `Ok(())` if successful, or an error if it fails. - pub fn exec(cmd: Cmd) -> io::Result<()> { - let ansi = match cmd { - Cmd::ShowCursor => csi!("?25h"), - Cmd::HideCursor => csi!("?25l"), - Cmd::MoveCursor(x, y) => csi!(&format!("{y};{x}H")), - Cmd::MoveCursorToHome => csi!("H"), - Cmd::ClearScreen => csi!("2J"), - Cmd::EnableAlternativeScreen => csi!("?1049h"), - Cmd::DisableAlternativeScreen => csi!("?1049l"), - Cmd::EnableMouseReporting => csi!("?1000h"), - Cmd::DisableMouseReporting => csi!("?1000l"), - Cmd::EnableSgrCoords => csi!("?1006h"), - Cmd::DisableSgrCoords => csi!("?1006l"), - }; - print!("{ansi}"); - io::stdout().flush() + pub(crate) fn exec(cmd: Cmd) -> io::Result<()> { + let stdout = io::stdout(); + let mut lock = stdout.lock(); + match cmd { + Cmd::ShowCursor => lock.write_all(b"\x1B[?25h")?, + Cmd::HideCursor => lock.write_all(b"\x1B[?25l")?, + Cmd::ClearScreen => lock.write_all(b"\x1B[2J")?, + Cmd::EnableAlternativeScreen => lock.write_all(b"\x1B[?1049h")?, + Cmd::DisableAlternativeScreen => lock.write_all(b"\x1B[?1049l")?, + Cmd::EnableMouseReporting => lock.write_all(b"\x1B[?1000h")?, + Cmd::DisableMouseReporting => lock.write_all(b"\x1B[?1000l")?, + Cmd::EnableSgrCoords => lock.write_all(b"\x1B[?1006h")?, + Cmd::DisableSgrCoords => lock.write_all(b"\x1B[?1006l")?, + } + lock.flush() } /// Get the terminal size - pub fn get_size(&self) -> io::Result<(usize, usize)> { + pub(crate) fn get_size(&self) -> io::Result<(usize, usize)> { let mut ws = libc::winsize { ws_row: 0, ws_col: 0, diff --git a/src/window.rs b/src/window.rs index 30101f4..f8d9dc9 100644 --- a/src/window.rs +++ b/src/window.rs @@ -173,7 +173,7 @@ impl Drop for Window { } /// A listener for window size changes -pub struct WindowSizeListener { +struct WindowSizeListener { /// The thread handle for the listener handle: Option>, /// The stop signal for the listener @@ -188,7 +188,7 @@ impl WindowSizeListener { /// * `rate`: The rate at which to check for window size changes /// /// Returns `WindowSizeListener` instance. - pub fn new(terminal: Terminal, rate: Duration) -> Self { + fn new(terminal: Terminal, rate: Duration) -> Self { #[allow(clippy::type_complexity)] let (size_tx, size_rx): (SyncSender<(usize, usize)>, Receiver<(usize, usize)>) = mpsc::sync_channel(1); @@ -227,14 +227,14 @@ impl WindowSizeListener { /// Try to receive a window size change event /// /// Returns the new window size if available, or an error if not. - pub fn try_recv(&self) -> Result<(usize, usize), mpsc::TryRecvError> { + fn try_recv(&self) -> Result<(usize, usize), mpsc::TryRecvError> { self.size_rx.try_recv() } /// Stop the window size listener /// /// Returns `Ok(())` if the listener was stopped successfully, or an error if it failed. - pub fn stop(&mut self) -> Result<(), Box> { + fn stop(&mut self) -> Result<(), Box> { if let Some(tx) = self.stop_signal.take() { tx.send(())?; // Send stop signal } From 2b5c5c7d4e4ccf30f502e216ad590d8000af3ae7 Mon Sep 17 00:00:00 2001 From: broccolingual Date: Wed, 11 Mar 2026 07:21:29 +0000 Subject: [PATCH 2/3] fix: increase CHUNK_SIZE to improve framebuffer performance --- src/framebuffer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/framebuffer.rs b/src/framebuffer.rs index 88e2890..42b34c7 100644 --- a/src/framebuffer.rs +++ b/src/framebuffer.rs @@ -3,7 +3,7 @@ use std::io::{self, Write}; use crate::{Attr, Color}; -const CHUNK_SIZE: usize = 256; +const CHUNK_SIZE: usize = 1024; /// Represents a single cell in the framebuffer. #[derive(Clone, Copy, PartialEq, Debug)] From 3fa893d8e852f572d4d0a934b3bb91e32b9a548f Mon Sep 17 00:00:00 2001 From: broccolingual Date: Wed, 11 Mar 2026 07:28:03 +0000 Subject: [PATCH 3/3] fix: ensure render thread yields correctly when back framebuffer is locked --- src/render.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/render.rs b/src/render.rs index c2631ba..9559ca2 100644 --- a/src/render.rs +++ b/src/render.rs @@ -65,6 +65,7 @@ impl RenderThread { } Err(TryLockError::WouldBlock) => { thread::yield_now(); // Yield to other threads if the back framebuffer is currently locked + continue; } Err(_) => { break;