feat(no_std)!: option to disable layout cache for no_std compatibility (#1795)

Resolves #1780 

BREAKING CHANGE: 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.
This commit is contained in:
Jagoda Estera Ślązak 2025-05-07 22:00:22 +02:00 committed by GitHub
parent 09173d1829
commit ab48c06171
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 69 additions and 17 deletions

View File

@ -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<B>` 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<Item = SparklineBar>` 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

View File

@ -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"]

View File

@ -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<Cache> = RefCell::new(Cache::new(
#[cfg(feature = "layout-cache")]
std::thread_local! {
static LAYOUT_CACHE: core::cell::RefCell<Cache> = 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);
});
}

View File

@ -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"]

View File

@ -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",