From b9570962cfc214bbac0f3fef1e6c1f9848f52658 Mon Sep 17 00:00:00 2001 From: Mica White Date: Wed, 17 Jun 2026 17:41:28 -0400 Subject: Basic renderer and framerate test --- src/buffer.rs | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/main.rs | 47 +++++++++++++++++++++++++++++- 3 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 src/buffer.rs create mode 100644 src/lib.rs (limited to 'src') diff --git a/src/buffer.rs b/src/buffer.rs new file mode 100644 index 0000000..fa0feaa --- /dev/null +++ b/src/buffer.rs @@ -0,0 +1,92 @@ +use std::io::Write; + +use crossterm::{ + cursor::{Hide, MoveTo, Show}, + event::{self, Event}, + execute, + style::{ContentStyle, PrintStyledContent, StyledContent}, + terminal::{ + BeginSynchronizedUpdate, Clear, ClearType, EndSynchronizedUpdate, EnterAlternateScreen, + LeaveAlternateScreen, WindowSize, disable_raw_mode, enable_raw_mode, window_size, + }, +}; + +#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)] +pub struct Cell { + pub character: char, + pub style: ContentStyle, + pub x: u16, + pub y: u16, + pub z: u8, +} + +#[derive(Debug)] +pub struct Buffer<'a, W: Write> { + stdout: &'a mut W, + pub cells: Vec, + pub window_size: WindowSize, +} + +impl<'a, W: Write> Buffer<'a, W> { + pub fn new(stdout: &'a mut W) -> std::io::Result { + Ok(Self { + stdout, + cells: Vec::new(), + window_size: window_size()?, + }) + } + + fn initialize(writer: &mut impl Write) -> std::io::Result<()> { + enable_raw_mode()?; + execute!(writer, Hide, EnterAlternateScreen)?; + Ok(()) + } + + fn render(&mut self) -> std::io::Result<()> { + self.cells.sort_unstable_by_key(|cell| cell.z); + execute!(self.stdout, BeginSynchronizedUpdate, Clear(ClearType::All))?; + for cell in &self.cells { + execute!( + self.stdout, + MoveTo(cell.x, cell.y), + PrintStyledContent(StyledContent::new(cell.style, cell.character)) + )?; + } + execute!(self.stdout, EndSynchronizedUpdate)?; + Ok(()) + } + + pub fn finish(&mut self) -> std::io::Result<()> { + execute!(self.stdout, Show, LeaveAlternateScreen)?; + disable_raw_mode()?; + Ok(()) + } + + pub fn render_loop( + &mut self, + mut event_handler: impl FnMut(&mut Self, Event), + mut update: impl FnMut(&mut Self), + ) -> std::io::Result<()> { + Self::initialize(self.stdout)?; + let (sender, receiver) = std::sync::mpsc::channel(); + std::thread::scope(|scope| -> std::io::Result<()> { + scope.spawn(|| { + loop { + let Ok(event) = event::read() else { + continue; + }; + let _ = sender.send(event); + } + }); + loop { + while let Ok(event) = receiver.try_recv() { + event_handler(self, event); + } + update(self); + self.render()?; + } + })?; + self.finish()?; + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..86b6256 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +pub mod buffer; diff --git a/src/main.rs b/src/main.rs index e7a11a9..e8c9f23 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,48 @@ +use std::time::Instant; + +use crossterm::{ + event::{Event, KeyCode}, + style::ContentStyle, +}; +use tortuise::buffer::{Buffer, Cell}; + fn main() { - println!("Hello, world!"); + let mut stdout = std::io::stdout().lock(); + let mut buffer = Buffer::new(&mut stdout).unwrap(); + let mut last_frame_start = Instant::now(); + buffer + .render_loop( + |renderer, event| { + if let Event::Key(event) = event + && event.code == KeyCode::Esc + { + let _ = renderer.finish(); + std::process::exit(0); + } + }, + |renderer| { + renderer.cells.clear(); + let end = Instant::now(); + let frame_time = (end - last_frame_start).as_micros(); + let digits = [ + frame_time % 1_000_000 / 100_000, + frame_time % 100_000 / 10_000, + frame_time % 10_000 / 1_000, + frame_time % 1_000 / 100, + frame_time % 100 / 10, + frame_time % 10, + ]; + for (x, digit) in digits.into_iter().enumerate() { + renderer.cells.push(Cell { + character: char::from_digit(digit as u32, 10).unwrap(), + x: x as u16, + y: 0, + z: 0, + style: ContentStyle::new(), + }); + } + last_frame_start = Instant::now(); + }, + ) + .unwrap(); } -- cgit v1.2.3