diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 338bc53a..742d40b8 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -20,6 +20,8 @@ This is a quick summary of the sections below: - `Backend` now uses `Self::Error` for error handling instead of `std::io::Error` - `Terminal` now uses `B::Error` for error handling instead of `std::io::Error` - `TestBackend` now uses `core::convert::Infallible` for error handling instead of `std::io::Error` + - Disabling `default-features` will now disable layout cache, which can have a negative impact on performance + - `Layout::init_cache` and `Layout::DEFAULT_CACHE_SIZE` are now only available if `layout-cache` feature is enabled - [v0.29.0](#v0290) - `Sparkline::data` takes `IntoIterator` instead of `&[u64]` and is no longer const - Removed public fields from `Rect` iterators @@ -84,6 +86,26 @@ This is a quick summary of the sections below: ## Unreleased (0.30.0) +### `Layout::init_cache` and `Layout::DEFAULT_CACHE_SIZE` are now only available if `layout-cache` feature is enabled ([#1795]) + +[#1795]: https://github.com/ratatui/ratatui/pull/1795 + +Previously, `Layout::init_cache` and `Layout::DEFAULT_CACHE_SIZE` were available independently of +enabled feature flags. + +### Disabling `default-features` will now disable layout cache, which can have a negative impact on performance ([#1795]) + +[#1795]: https://github.com/ratatui/ratatui/pull/1795 + +Layout cache is now opt-in in `ratatui-core` and enabled by default in `ratatui`. If app doesn't +make use of `no_std`-compatibility, and disables `default-feature`, it is recommended to explicitly +re-enable layout cache. Not doing so may impact performance. + +```diff +- ratatui = { version = "0.29.0", default-features = false } ++ ratatui = { version = "0.30.0", default-features = false, features = ["layout-cache"] } +``` + ### `TestBackend` now uses `core::convert::Infallible` for error handling instead of `std::io::Error` ([#1823]) [#1823]: https://github.com/ratatui/ratatui/pull/1823 diff --git a/ratatui-core/Cargo.toml b/ratatui-core/Cargo.toml index ba6c2337..bfd27709 100644 --- a/ratatui-core/Cargo.toml +++ b/ratatui-core/Cargo.toml @@ -24,6 +24,12 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = [] +## enables std +std = [] + +## enables layout cache +layout-cache = ["std"] + ## enables conversions to / from colors, modifiers, and styles in the ['anstyle'] crate anstyle = ["dep:anstyle"] diff --git a/ratatui-core/src/layout/layout.rs b/ratatui-core/src/layout/layout.rs index 7da86c4b..9cb23828 100644 --- a/ratatui-core/src/layout/layout.rs +++ b/ratatui-core/src/layout/layout.rs @@ -1,10 +1,9 @@ use alloc::format; use alloc::rc::Rc; use alloc::vec::Vec; -use core::cell::RefCell; use core::iter; use core::num::NonZeroUsize; -use std::{dbg, thread_local}; +use std::dbg; use hashbrown::HashMap; use itertools::Itertools; @@ -39,8 +38,9 @@ type Cache = LruCache<(Rect, Layout), (Segments, Spacers)>; // calculations. const FLOAT_PRECISION_MULTIPLIER: f64 = 100.0; -thread_local! { - static LAYOUT_CACHE: RefCell = RefCell::new(Cache::new( +#[cfg(feature = "layout-cache")] +std::thread_local! { + static LAYOUT_CACHE: core::cell::RefCell = core::cell::RefCell::new(Cache::new( NonZeroUsize::new(Layout::DEFAULT_CACHE_SIZE).unwrap(), )); } @@ -188,6 +188,8 @@ impl Layout { /// bit more to make it a round number. This gives enough entries to store a layout for every /// row and every column, twice over, which should be enough for most apps. For those that need /// more, the cache size can be set with [`Layout::init_cache()`]. + /// This const is unused if layout cache is disabled. + #[cfg(feature = "layout-cache")] pub const DEFAULT_CACHE_SIZE: usize = 500; /// Creates a new layout with default values. @@ -280,8 +282,9 @@ impl Layout { /// grows until `cache_size` is reached. /// /// By default, the cache size is [`Self::DEFAULT_CACHE_SIZE`]. + #[cfg(feature = "layout-cache")] pub fn init_cache(cache_size: NonZeroUsize) { - LAYOUT_CACHE.with_borrow_mut(|c| c.resize(cache_size)); + LAYOUT_CACHE.with_borrow_mut(|cache| cache.resize(cache_size)); } /// Set the direction of the layout. @@ -653,11 +656,18 @@ impl Layout { /// ); /// ``` pub fn split_with_spacers(&self, area: Rect) -> (Segments, Spacers) { - LAYOUT_CACHE.with_borrow_mut(|c| { - let key = (area, self.clone()); - c.get_or_insert(key, || self.try_split(area).expect("failed to split")) - .clone() - }) + let split = || self.try_split(area).expect("failed to split"); + + #[cfg(feature = "layout-cache")] + { + LAYOUT_CACHE.with_borrow_mut(|cache| { + let key = (area, self.clone()); + cache.get_or_insert(key, split).clone() + }) + } + + #[cfg(not(feature = "layout-cache"))] + split() } fn try_split(&self, area: Rect) -> Result<(Segments, Spacers), AddConstraintError> { @@ -1200,14 +1210,15 @@ mod tests { } #[test] + #[cfg(feature = "layout-cache")] fn cache_size() { - LAYOUT_CACHE.with_borrow(|c| { - assert_eq!(c.cap().get(), Layout::DEFAULT_CACHE_SIZE); + LAYOUT_CACHE.with_borrow(|cache| { + assert_eq!(cache.cap().get(), Layout::DEFAULT_CACHE_SIZE); }); Layout::init_cache(NonZeroUsize::new(10).unwrap()); - LAYOUT_CACHE.with_borrow(|c| { - assert_eq!(c.cap().get(), 10); + LAYOUT_CACHE.with_borrow(|cache| { + assert_eq!(cache.cap().get(), 10); }); } diff --git a/ratatui/Cargo.toml b/ratatui/Cargo.toml index 13940c3d..617e0068 100644 --- a/ratatui/Cargo.toml +++ b/ratatui/Cargo.toml @@ -25,9 +25,10 @@ rustdoc-args = ["--cfg", "docsrs"] #! ## By default, we enable the crossterm backend as this is a reasonable choice for most applications ## as it is supported on Linux/Mac/Windows systems. We also enable the `underline-color` feature -## which allows you to set the underline color of text and the `macros` feature which provides -## some useful macros. -default = ["crossterm", "underline-color", "all-widgets", "macros"] +## which allows you to set the underline color of text, the `macros` feature which provides +## some useful macros and `layout-cache` which speeds up layout cache calculations +## in `std`-enabled environments. +default = ["crossterm", "underline-color", "all-widgets", "macros", "layout-cache"] #! Generally an application will only use one backend, so you should only enable one of the following features: ## enables the [`CrosstermBackend`](backend::CrosstermBackend) backend and adds a dependency on [`crossterm`]. crossterm = ["dep:ratatui-crossterm"] @@ -48,6 +49,9 @@ serde = [ "ratatui-termwiz?/serde", ] +## enables layout cache +layout-cache = ["ratatui-core/layout-cache"] + ## enables conversions from colors in the [`palette`] crate to [`Color`](crate::style::Color). palette = ["ratatui-core/palette", "dep:palette"] diff --git a/xtask/src/commands/backend.rs b/xtask/src/commands/backend.rs index 245c9d81..5a7d2fa3 100644 --- a/xtask/src/commands/backend.rs +++ b/xtask/src/commands/backend.rs @@ -54,6 +54,15 @@ impl Run for TestBackend { Backend::Termion => "termion", Backend::Termwiz => "termwiz", }; + // This is a temporary hack to run tests both with and without layout cache. + // https://github.com/ratatui/ratatui/issues/1820 + run_cargo(vec![ + "test", + "--all-targets", + "--no-default-features", + "--features", + format!("{backend},layout-cache").as_str(), + ])?; run_cargo(vec![ "test", "--all-targets",