use std::ops::{Deref, DerefMut};
use super::EditorBuffer;
use crate::{engine_public_api, EditorEngine, HasFocus, RenderArgs, RenderOps, ScrOfs,
Size};
pub(in crate::tui::editor::editor_buffer) mod key {
use super::{ScrOfs, Size};
#[derive(Clone, Debug, PartialEq)]
pub struct Key((ScrOfs, Size));
impl Key {
#[must_use]
pub fn new(scr_ofs: ScrOfs, window_size: Size) -> Self {
(scr_ofs, window_size).into()
}
}
impl From<(ScrOfs, Size)> for Key {
fn from((scr_ofs, window_size): (ScrOfs, Size)) -> Self {
Self((scr_ofs, window_size))
}
}
}
pub use key::*;
pub(in crate::tui::editor::editor_buffer) mod cache_entry {
use super::{key::Key, RenderOps};
#[derive(Clone, Debug, PartialEq)]
pub struct CacheEntry(pub Key, pub RenderOps);
impl CacheEntry {
pub fn new(arg_key: impl Into<Key>, value: RenderOps) -> Self {
Self(arg_key.into(), value)
}
}
}
pub use cache_entry::*;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum UseRenderCache {
Yes,
No,
}
#[derive(Clone, Default, Debug, PartialEq)]
pub struct RenderCache {
pub entry: Option<cache_entry::CacheEntry>,
}
mod render_cache_impl_block {
use super::{cache_entry, engine_public_api, CacheEntry, Deref, DerefMut,
EditorBuffer, EditorEngine, HasFocus, Key, RenderArgs, RenderCache,
RenderOps, Size, UseRenderCache};
impl Deref for RenderCache {
type Target = Option<cache_entry::CacheEntry>;
fn deref(&self) -> &Self::Target { &self.entry }
}
impl DerefMut for RenderCache {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.entry }
}
impl RenderCache {
pub fn clear(&mut self) { self.entry = None; }
pub fn get(&self, arg_key: impl Into<Key>) -> Option<&RenderOps> {
let key: Key = arg_key.into();
if key == self.entry.as_ref()?.0 {
Some(&self.entry.as_ref()?.1)
} else {
None
}
}
pub fn insert(&mut self, arg_key: impl Into<Key>, value: RenderOps) {
let key: Key = arg_key.into();
self.entry = Some(CacheEntry::new(key, value));
}
pub fn render_content(
buffer: &mut EditorBuffer,
engine: &mut EditorEngine,
window_size: Size,
has_focus: &mut HasFocus,
render_ops: &mut RenderOps,
use_cache: UseRenderCache,
) {
if matches!(use_cache, UseRenderCache::Yes)
&& let Some(cached_output) =
buffer.render_cache.get((buffer.get_scr_ofs(), window_size))
{
*render_ops = cached_output.clone();
return;
}
engine_public_api::render_content(
RenderArgs {
engine,
buffer,
has_focus,
},
render_ops,
);
match use_cache {
UseRenderCache::Yes => buffer
.render_cache
.insert((buffer.get_scr_ofs(), window_size), render_ops.clone()),
UseRenderCache::No => buffer.render_cache.clear(),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{assert_eq2, col, height, render_ops, row, scr_ofs, width, RenderOp};
fn get_render_ops_og() -> RenderOps {
render_ops!(
@new
RenderOp::ClearScreen, RenderOp::ResetColor
)
}
fn get_window_size_og() -> Size { height(70) + width(15) }
#[test]
fn test_cache_can_be_disabled() {
let buffer = &mut EditorBuffer::default();
let engine = &mut EditorEngine::default();
let has_focus = &mut HasFocus::default();
assert_eq2!(
buffer
.render_cache
.get((buffer.get_scr_ofs(), get_window_size_og())),
None
);
let render_ops_mut = &mut get_render_ops_og();
RenderCache::render_content(
buffer,
engine,
get_window_size_og(),
has_focus,
render_ops_mut,
UseRenderCache::Yes,
);
assert_eq2!(
buffer
.render_cache
.get((buffer.get_scr_ofs(), get_window_size_og())),
Some(&get_render_ops_og())
);
RenderCache::render_content(
buffer,
engine,
get_window_size_og(),
has_focus,
render_ops_mut,
UseRenderCache::No,
);
assert_eq2!(
buffer
.render_cache
.get((buffer.get_scr_ofs(), get_window_size_og())),
None
);
}
#[test]
fn test_assert_cache_hit_for_multiple_renders() {
let buffer = &mut EditorBuffer::default();
let engine = &mut EditorEngine::default();
let has_focus = &mut HasFocus::default();
assert_eq2!(
buffer
.render_cache
.get((buffer.get_scr_ofs(), get_window_size_og())),
None
);
let render_ops_mut = &mut get_render_ops_og();
RenderCache::render_content(
buffer,
engine,
get_window_size_og(),
has_focus,
render_ops_mut,
UseRenderCache::Yes,
);
assert_eq2!(
buffer
.render_cache
.get((buffer.get_scr_ofs(), get_window_size_og())),
Some(&get_render_ops_og())
);
RenderCache::render_content(
buffer,
engine,
get_window_size_og(),
has_focus,
render_ops_mut,
UseRenderCache::Yes,
);
assert_eq2!(
buffer
.render_cache
.get((buffer.get_scr_ofs(), get_window_size_og())),
Some(&get_render_ops_og())
);
render_ops_mut.clear();
assert!(render_ops_mut.is_empty());
RenderCache::render_content(
buffer,
engine,
get_window_size_og(),
has_focus,
render_ops_mut,
UseRenderCache::Yes,
);
assert!(!render_ops_mut.is_empty());
assert_eq2!(render_ops_mut, &get_render_ops_og());
assert_eq2!(
buffer
.render_cache
.get((buffer.get_scr_ofs(), get_window_size_og())),
Some(&get_render_ops_og())
);
}
#[test]
fn test_assert_cache_miss_for_first_render() {
let buffer = &mut EditorBuffer::default();
let engine = &mut EditorEngine::default();
let has_focus = &mut HasFocus::default();
assert_eq2!(
buffer
.render_cache
.get((buffer.get_scr_ofs(), get_window_size_og())),
None
);
let render_ops_mut = &mut get_render_ops_og();
RenderCache::render_content(
buffer,
engine,
get_window_size_og(),
has_focus,
render_ops_mut,
UseRenderCache::Yes,
);
assert_eq2!(
buffer
.render_cache
.get((buffer.get_scr_ofs(), get_window_size_og())),
Some(&get_render_ops_og())
);
render_ops_mut.clear();
assert!(render_ops_mut.is_empty());
RenderCache::render_content(
buffer,
engine,
get_window_size_og(),
has_focus,
render_ops_mut,
UseRenderCache::Yes,
);
assert!(!render_ops_mut.is_empty());
assert_eq2!(render_ops_mut, &get_render_ops_og());
assert_eq2!(
buffer
.render_cache
.get((buffer.get_scr_ofs(), get_window_size_og())),
Some(&get_render_ops_og())
);
}
#[test]
fn test_window_size_change_causes_cache_miss() {
let buffer = &mut EditorBuffer::default();
let engine = &mut EditorEngine::default();
let has_focus = &mut HasFocus::default();
let render_ops_mut = &mut get_render_ops_og();
RenderCache::render_content(
buffer,
engine,
get_window_size_og(),
has_focus,
render_ops_mut,
UseRenderCache::Yes,
);
let window_size_new = height(50) + width(15);
assert!(window_size_new != get_window_size_og());
RenderCache::render_content(
buffer,
engine,
window_size_new,
has_focus,
render_ops_mut,
UseRenderCache::Yes,
);
assert_eq2!(
buffer
.render_cache
.get((buffer.get_scr_ofs(), get_window_size_og())),
None
);
assert_eq2!(
buffer
.render_cache
.get((buffer.get_scr_ofs(), window_size_new)),
Some(&get_render_ops_og())
);
}
#[test]
fn test_scroll_offset_change_causes_cache_miss() {
let buffer = &mut EditorBuffer::default();
let engine = &mut EditorEngine::default();
let has_focus = &mut HasFocus::default();
let render_ops_mut = &mut get_render_ops_og();
RenderCache::render_content(
buffer,
engine,
get_window_size_og(),
has_focus,
render_ops_mut,
UseRenderCache::Yes,
);
let scr_ofs_old = buffer.get_scr_ofs();
let scr_ofs_new = scr_ofs(col(1) + row(1));
assert!(scr_ofs_new != scr_ofs_old);
buffer.content.scr_ofs = scr_ofs_new;
RenderCache::render_content(
buffer,
engine,
get_window_size_og(),
has_focus,
render_ops_mut,
UseRenderCache::Yes,
);
assert_eq2!(
buffer.render_cache.get((scr_ofs_old, get_window_size_og())),
None
);
assert_eq2!(
buffer.render_cache.get((scr_ofs_new, get_window_size_og())),
Some(&get_render_ops_og())
);
}
#[test]
fn test_content_change_invalidates_cache() {
let buffer = &mut EditorBuffer::default();
let engine = &mut EditorEngine::default();
let has_focus = &mut HasFocus::default();
let snapshot_1 = {
buffer.init_with(["r3bl"]);
RenderCache::render_content(
buffer,
engine,
get_window_size_og(),
has_focus,
&mut get_render_ops_og(),
UseRenderCache::Yes,
);
assert!(buffer
.render_cache
.get((buffer.get_scr_ofs(), get_window_size_og()))
.is_some());
buffer
.render_cache
.get((buffer.get_scr_ofs(), get_window_size_og()))
.unwrap()
.clone()
};
let snapshot_2 = {
buffer.init_with(["r3bl", "r3bl"]);
RenderCache::render_content(
buffer,
engine,
get_window_size_og(),
has_focus,
&mut get_render_ops_og(),
UseRenderCache::Yes,
);
assert!(buffer
.render_cache
.get((buffer.get_scr_ofs(), get_window_size_og()))
.is_some());
buffer
.render_cache
.get((buffer.get_scr_ofs(), get_window_size_og()))
.unwrap()
.clone()
};
assert!(snapshot_1 != snapshot_2);
}
}