mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-22 04:33:13 +00:00
fix(Layout): use LruCache for layout cache (#487)
The layout cache now uses a LruCache with default size set to 16 entries. Previously the cache was backed by a HashMap, and was able to grow without bounds as a new entry was added for every new combination of layout parameters. - Added a new method (`layout::init_cache(usize)`) that allows the cache size to be changed if necessary. This will only have an effect if it is called prior to any calls to `layout::split()` as the cache is wrapped in a `OnceLock`
This commit is contained in:
parent
d4976d4b63
commit
638d596a3b
2 changed files with 77 additions and 8 deletions
|
@ -44,6 +44,7 @@ time = { version = "0.3.11", optional = true, features = ["local-offset"] }
|
|||
unicode-segmentation = "1.10"
|
||||
unicode-width = "0.1"
|
||||
document-features = { version = "0.2.7", optional = true }
|
||||
lru = "0.11.1"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.71"
|
||||
|
|
|
@ -3,7 +3,9 @@ use std::{
|
|||
cmp::{max, min},
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
num::NonZeroUsize,
|
||||
rc::Rc,
|
||||
sync::OnceLock,
|
||||
};
|
||||
|
||||
use cassowary::{
|
||||
|
@ -12,6 +14,7 @@ use cassowary::{
|
|||
WeightedRelation::{EQ, GE, LE},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use lru::LruCache;
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
#[derive(Debug, Default, Display, EnumString, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
|
@ -337,6 +340,7 @@ impl Default for Layout {
|
|||
}
|
||||
|
||||
impl Layout {
|
||||
pub const DEFAULT_CACHE_SIZE: usize = 16;
|
||||
/// Creates a new layout with default values.
|
||||
///
|
||||
/// - direction: [Direction::Vertical]
|
||||
|
@ -355,6 +359,28 @@ impl Layout {
|
|||
}
|
||||
}
|
||||
|
||||
/// Initialize an empty cache with a custom size. The cache is keyed on the layout and area, so
|
||||
/// that subsequent calls with the same parameters are faster. The cache is a LruCache, and
|
||||
/// grows until `cache_size` is reached.
|
||||
///
|
||||
/// Returns true if the cell's value was set by this call.
|
||||
/// Returns false if the cell's value was not set by this call, this means that another thread
|
||||
/// has set this value or that the cache size is already initialized.
|
||||
///
|
||||
/// Note that a custom cache size will be set only if this function:
|
||||
/// * is called before [Layout::split()] otherwise, the cache size is
|
||||
/// [`Self::DEFAULT_CACHE_SIZE`].
|
||||
/// * is called for the first time, subsequent calls do not modify the cache size.
|
||||
pub fn init_cache(cache_size: usize) -> bool {
|
||||
LAYOUT_CACHE
|
||||
.with(|c| {
|
||||
c.set(RefCell::new(LruCache::new(
|
||||
NonZeroUsize::new(cache_size).unwrap(),
|
||||
)))
|
||||
})
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Builder method to set the constraints of the layout.
|
||||
///
|
||||
/// # Examples
|
||||
|
@ -474,7 +500,8 @@ impl Layout {
|
|||
///
|
||||
/// This method stores the result of the computation in a thread-local cache keyed on the layout
|
||||
/// and area, so that subsequent calls with the same parameters are faster. The cache is a
|
||||
/// simple HashMap, and grows indefinitely (<https://github.com/ratatui-org/ratatui/issues/402>).
|
||||
/// LruCache, and grows until [`Self::DEFAULT_CACHE_SIZE`] is reached by default, if the cache
|
||||
/// is initialized with the [Layout::init_cache()] grows until the initialized cache size.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -494,18 +521,21 @@ impl Layout {
|
|||
/// ```
|
||||
pub fn split(&self, area: Rect) -> Rc<[Rect]> {
|
||||
LAYOUT_CACHE.with(|c| {
|
||||
c.borrow_mut()
|
||||
.entry((area, self.clone()))
|
||||
.or_insert_with(|| split(area, self))
|
||||
.clone()
|
||||
c.get_or_init(|| {
|
||||
RefCell::new(LruCache::new(
|
||||
NonZeroUsize::new(Self::DEFAULT_CACHE_SIZE).unwrap(),
|
||||
))
|
||||
})
|
||||
.borrow_mut()
|
||||
.get_or_insert((area, self.clone()), || split(area, self))
|
||||
.clone()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type Cache = HashMap<(Rect, Layout), Rc<[Rect]>>;
|
||||
type Cache = LruCache<(Rect, Layout), Rc<[Rect]>>;
|
||||
thread_local! {
|
||||
// TODO: Maybe use a fixed size cache https://github.com/ratatui-org/ratatui/issues/402
|
||||
static LAYOUT_CACHE: RefCell<Cache> = RefCell::new(HashMap::new());
|
||||
static LAYOUT_CACHE: OnceLock<RefCell<Cache>> = OnceLock::new();
|
||||
}
|
||||
|
||||
/// A container used by the solver inside split
|
||||
|
@ -667,6 +697,44 @@ mod tests {
|
|||
use super::{SegmentSize::*, *};
|
||||
use crate::prelude::Constraint::*;
|
||||
|
||||
#[test]
|
||||
fn custom_cache_size() {
|
||||
assert!(Layout::init_cache(10));
|
||||
assert!(!Layout::init_cache(15));
|
||||
LAYOUT_CACHE.with(|c| {
|
||||
assert_eq!(c.get().unwrap().borrow().cap().get(), 10);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_cache_size() {
|
||||
let target = Rect {
|
||||
x: 2,
|
||||
y: 2,
|
||||
width: 10,
|
||||
height: 10,
|
||||
};
|
||||
|
||||
Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Percentage(10),
|
||||
Constraint::Max(5),
|
||||
Constraint::Min(1),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(target);
|
||||
assert!(!Layout::init_cache(15));
|
||||
LAYOUT_CACHE.with(|c| {
|
||||
assert_eq!(
|
||||
c.get().unwrap().borrow().cap().get(),
|
||||
Layout::DEFAULT_CACHE_SIZE
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn corner_to_string() {
|
||||
assert_eq!(Corner::BottomLeft.to_string(), "BottomLeft");
|
||||
|
|
Loading…
Reference in a new issue