rsvim_core 0.1.3-alpha.2

The core library for RSVIM text editor.
Documentation
//! Internal cache for `Text`:
//!
//! - Lines width cache
//! - Cloned lines cache.
//! - Cache hit/miss statistics.

use crate::buf::text::cidx::ColumnIndex;
use crate::prelude::*;
use arcstr::ArcStr;
use lru::LruCache;
use std::hash::Hash;

#[cfg(test)]
#[derive(Debug, Default)]
pub struct Stats {
  hits: usize,
  misses: usize,
}

#[cfg(test)]
impl Stats {
  pub fn hit(&mut self) {
    self.hits += 1;
  }

  pub fn miss(&mut self) {
    self.misses += 1;
  }

  pub fn hits(&self) -> usize {
    self.hits
  }

  pub fn misses(&self) -> usize {
    self.misses
  }

  pub fn total(&self) -> usize {
    self.hits + self.misses
  }

  pub fn ratio(&self) -> f32 {
    if self.total() == 0 {
      0_f32
    } else {
      self.hits as f32 / (self.total() as f32)
    }
  }
}

#[cfg(test)]
impl std::fmt::Display for Stats {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    if self.total() == 0 {
      Ok(())
    } else {
      f.write_fmt(format_args!(
        "hit/miss/total:{}/{}/{},hit ratio:{}",
        self.hits,
        self.misses,
        self.total(),
        self.ratio(),
      ))
    }
  }
}

fn _cached_size(canvas_size: U16Size) -> std::num::NonZeroUsize {
  std::num::NonZeroUsize::new(canvas_size.height() as usize * 3 + 3).unwrap()
}

#[derive(Debug)]
// Internal cache implementation.
pub struct GenericCache<K: Copy + Eq + Hash, V> {
  cache: LruCache<K, V, RandomState>,

  #[cfg(test)]
  stats: Stats,
}

impl<K: Copy + Eq + Hash, V> GenericCache<K, V> {
  pub fn new(canvas_size: U16Size) -> Self {
    let cache_size = _cached_size(canvas_size);
    Self {
      cache: LruCache::with_hasher(cache_size, RandomState::default()),

      #[cfg(test)]
      stats: Stats::default(),
    }
  }

  #[cfg(test)]
  pub fn stats(&self) -> &Stats {
    &self.stats
  }

  fn _get_or_insert_impl<F>(&mut self, k: &K, f: F)
  where
    F: FnOnce() -> Option<V>,
  {
    #[cfg(test)]
    {
      if self.cache.contains(k) {
        self.stats.hit();
      } else {
        self.stats.miss();
      }
    }

    if !self.cache.contains(k)
      && let Some(v) = f()
    {
      self.cache.put(*k, v);
    }
  }

  pub fn get_or_insert<F>(&mut self, k: &K, f: F) -> Option<&V>
  where
    F: FnOnce() -> Option<V>,
  {
    self._get_or_insert_impl(k, f);
    self.cache.get(k)
  }

  pub fn get_or_insert_mut<F>(&mut self, k: &K, f: F) -> Option<&mut V>
  where
    F: FnOnce() -> Option<V>,
  {
    self._get_or_insert_impl(k, f);
    self.cache.get_mut(k)
  }

  pub fn resize(&mut self, canvas_size: U16Size) {
    let new_cache_size = _cached_size(canvas_size);
    if new_cache_size > self.cache.cap() {
      self.cache.resize(new_cache_size);
    }
  }

  pub fn clear(&mut self) {
    self.cache.clear();
  }

  pub fn retain<F>(&mut self, f: F)
  where
    F: Fn(/* key */ &K) -> bool,
  {
    let to_be_removed: Vec<K> = self
      .cache
      .iter()
      .filter(|(key, _)| !f(key))
      .map(|(key, _)| *key)
      .collect();
    for key in to_be_removed.iter() {
      self.cache.pop(key);
    }
  }
}

/// Cached lines width.
///
/// Key `line_idx` => Value `ColumnIndex`.
pub type CachedWidth = GenericCache<usize, ColumnIndex>;

/// Cache key for `CachedLines`.
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)]
pub struct CachedLinesKey {
  pub line_idx: usize,
  pub start_char_idx: usize,
  pub max_chars: usize,
}

/// Cached cloned lines.
///
/// Key `CachedLinesKey` (line_idx, start_char_idx, max_chars) => Value `ArcStr`.
pub type CachedLines = GenericCache<CachedLinesKey, ArcStr>;