Skip to main content

RenderState

Struct RenderState 

Source
pub struct RenderState<'alloc>(/* private fields */);
Expand description

Represents the state required to render a visible screen (a viewport) of a terminal instance.

This is stateful and optimized for repeated updates from a single terminal instance and only updating dirty regions of the screen.

The key design principle of this API is that it only needs read/write access to the terminal instance during the update call. This allows the render state to minimally impact terminal IO performance and also allows the renderer to be safely multi-threaded (as long as a lock is held during the update call to ensure exclusive access to the terminal instance).

The basic usage of this API is:

  1. Create an empty render state
  2. Update it from a terminal instance whenever you need.
  3. Read from the render state to get the data needed to draw your frame.

§Dirty Tracking

Dirty tracking is a key feature of the render state that allows renderers to efficiently determine what parts of the screen have changed and only redraw changed regions.

The render state API keeps track of dirty state at two independent layers: a global dirty state that indicates whether the entire frame is clean, partially dirty, or fully dirty, and a per-row dirty state that allows tracking which rows in a partially dirty frame have changed.

The user of the render state API is expected to unset both of these. The update call does not unset dirty state, it only updates it.

An extremely important detail: setting one dirty state doesn’t unset the other. For example, setting the global dirty state to false does not reset the row-level dirty flags. So, the caller of the render state API must be careful to manage both layers of dirty state correctly.

§Examples

§Creating and updating render state

// Create a terminal and render state, then update the render state
// from the terminal. The render state captures a snapshot of everything
// needed to draw a frame.
use libghostty_vt::{Terminal, TerminalOptions, RenderState};

let mut terminal = Terminal::new(TerminalOptions {
    cols: 40,
    rows: 5,
    max_scrollback: 10000,
}).unwrap();

let mut render_state = RenderState::new().unwrap();

// Feed some styled content into the terminal.
terminal.vt_write(b"Hello, \x1b[1;32mworld\x1b[0m!\r\n");
terminal.vt_write(b"\x1b[4munderlined\x1b[0m text\r\n");
terminal.vt_write(b"\x1b[38;2;255;128;0morange\x1b[0m\r\n");

assert!(render_state.update(&terminal).is_ok());

§Checking dirty state

// Check the global dirty state to decide how much work the renderer
// needs to do. After rendering, reset it to false.
let snapshot = render_state.update(&terminal).unwrap();

match snapshot.dirty().unwrap() {
    Dirty::Clean => println!("Frame is clean, nothing to draw."),
    Dirty::Partial => println!("Partial redraw needed."),
    Dirty::Full => println!("Full redraw needed."),
}

§Reading colors

// Retrieve colors (background, foreground, palette) from the render
// state. These are needed to resolve palette-indexed cell colors.
let snapshot = render_state.update(&terminal).unwrap();
let colors = snapshot.colors().unwrap();

println!(
    "Background: {:02x}{:02x}{:02x}",
    colors.background.r, colors.background.g, colors.background.b
);
println!(
    "Foreground: {:02x}{:02x}{:02x}",
    colors.background.r, colors.background.g, colors.background.b
);

§Reading cursor state

// Read cursor position and visual style from the render state.
use libghostty_vt::render::CursorViewport;
let snapshot = render_state.update(&terminal).unwrap();

if snapshot.cursor_visible().unwrap() {
    if let Some(CursorViewport { x, y, .. }) = snapshot.cursor_viewport().unwrap() {
        let style = snapshot.cursor_visual_style().unwrap();
        println!("Cursor at ({x}, {y}), style {style:?}");
    }
}

§Iterating rows and cells

// Iterate rows via the row iterator. For each dirty row, iterate its
// cells, read codepoints/graphemes and styles, and emit ANSI-colored
// output as a simple "renderer".
use libghostty_vt::render::{RowIterator, CellIterator};

// During setup:
let mut rows = RowIterator::new().unwrap();
let mut cells = CellIterator::new().unwrap();

// On each frame:
let snapshot = render_state.update(&terminal).unwrap();
let mut row_iter = rows.update(&snapshot);
let mut row_index = 0;

while let Some(row) = row_iter.next() {
    // Check per-row dirty state; a real renderer would skip clean rows.
    print!(
        "Row {row_index} [{}]",
        if row.dirty().unwrap() { "dirty" } else { "clean" }
    );

    // Get cells for this row (reuses the same cells handle).
    let mut cell_iter = cells.update(&row);
    while let Some(cell) = cell_iter.next() {
        let graphemes = cell.graphemes().unwrap();
        println!("{:?}", &graphemes);
    }
    row_index += 1;
    println!()
}

Implementations§

Source§

impl<'alloc> RenderState<'alloc>

Source

pub fn new() -> Result<Self>

Create a new render state instance.

Source

pub fn new_with_alloc<'ctx: 'alloc, Ctx>( alloc: &'alloc Allocator<'ctx, Ctx>, ) -> Result<Self>

Create a new render state instance with a custom allocator.

See the crate-level documentation regarding custom memory management and lifetimes.

Source

pub fn update<'cb>( &mut self, terminal: &Terminal<'alloc, 'cb>, ) -> Result<Snapshot<'alloc, '_>>

Update a render state instance from a terminal, returning a new snapshot.

This consumes terminal/screen dirty state in the same way as the internal render state update path.

§Errors

Returns Err(Error::OutOfMemory) if updating the state requires allocation and that allocation fails.

Trait Implementations§

Source§

impl<'alloc> Debug for RenderState<'alloc>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Drop for RenderState<'_>

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

§

impl<'alloc> Freeze for RenderState<'alloc>

§

impl<'alloc> RefUnwindSafe for RenderState<'alloc>

§

impl<'alloc> !Send for RenderState<'alloc>

§

impl<'alloc> !Sync for RenderState<'alloc>

§

impl<'alloc> Unpin for RenderState<'alloc>

§

impl<'alloc> UnsafeUnpin for RenderState<'alloc>

§

impl<'alloc> UnwindSafe for RenderState<'alloc>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.