use std::{cell::RefCell, collections::HashMap, rc::Rc};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum LazyLayoutKey {
User(u64),
Index(usize),
}
impl LazyLayoutKey {
const USER_TAG: u64 = 0b00 << 62;
const INDEX_TAG: u64 = 0b01 << 62;
const VALUE_MASK: u64 = (1u64 << 62) - 1;
#[inline]
pub fn to_slot_id(self) -> u64 {
match self {
LazyLayoutKey::User(k) => {
let value = Self::normalize_value(k, "User");
Self::USER_TAG | value
}
LazyLayoutKey::Index(i) => {
let value = Self::normalize_value(i as u64, "Index");
Self::INDEX_TAG | value
}
}
}
#[inline]
fn normalize_value(value: u64, kind: &'static str) -> u64 {
if value <= Self::VALUE_MASK {
value
} else {
log::warn!(
"LazyList {kind} key {value:#018x} exceeds 62 bits; mixing to 62 bits to avoid overflow"
);
Self::mix_to_value_bits(value)
}
}
#[inline]
fn mix_to_value_bits(mut value: u64) -> u64 {
value ^= value >> 33;
value = value.wrapping_mul(0xff51afd7ed558ccd);
value ^= value >> 33;
value = value.wrapping_mul(0xc4ceb9fe1a85ec53);
value ^= value >> 33;
value & Self::VALUE_MASK
}
#[inline]
pub fn is_user_key(self) -> bool {
matches!(self, LazyLayoutKey::User(_))
}
}
#[doc(hidden)]
pub struct LazyScopeMarker;
#[derive(Clone, Default)]
pub struct LazyItems {
count: usize,
key: Option<Rc<dyn Fn(usize) -> u64>>,
content_type: Option<Rc<dyn Fn(usize) -> u64>>,
}
impl LazyItems {
pub fn new(count: usize) -> Self {
Self {
count,
key: None,
content_type: None,
}
}
pub fn key(mut self, key: impl Fn(usize) -> u64 + 'static) -> Self {
self.key = Some(Rc::new(key));
self
}
pub fn content_type(mut self, content_type: impl Fn(usize) -> u64 + 'static) -> Self {
self.content_type = Some(Rc::new(content_type));
self
}
pub fn count(&self) -> usize {
self.count
}
pub fn key_fn(&self) -> Option<Rc<dyn Fn(usize) -> u64>> {
self.key.clone()
}
pub fn content_type_fn(&self) -> Option<Rc<dyn Fn(usize) -> u64>> {
self.content_type.clone()
}
}
impl From<usize> for LazyItems {
fn from(count: usize) -> Self {
Self::new(count)
}
}
pub trait LazyListScope {
fn item<F>(&mut self, content: F)
where
F: Fn() + 'static,
{
self.item_keyed(None, None, content);
}
fn item_keyed<F>(&mut self, key: Option<u64>, content_type: Option<u64>, content: F)
where
F: Fn() + 'static;
fn items<I, F>(&mut self, items: I, item_content: F)
where
I: Into<LazyItems>,
F: Fn(usize) + 'static;
}
pub struct LazyListInterval {
pub start_index: usize,
pub count: usize,
pub key: Option<Rc<dyn Fn(usize) -> u64>>,
pub content_type: Option<Rc<dyn Fn(usize) -> u64>>,
pub content: Rc<dyn Fn(usize)>,
}
impl std::fmt::Debug for LazyListInterval {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LazyListInterval")
.field("start_index", &self.start_index)
.field("count", &self.count)
.finish_non_exhaustive()
}
}
pub struct LazyListIntervalContent {
intervals: Vec<LazyListInterval>,
total_count: usize,
key_cache: RefCell<Option<HashMap<u64, usize>>>,
}
impl LazyListIntervalContent {
pub fn new() -> Self {
Self {
intervals: Vec::new(),
total_count: 0,
key_cache: RefCell::new(None),
}
}
fn invalidate_cache(&self) {
*self.key_cache.borrow_mut() = None;
}
fn ensure_cache(&self) {
let mut cache = self.key_cache.borrow_mut();
if cache.is_some() {
return;
}
let mut map = HashMap::with_capacity(self.total_count);
for index in 0..self.total_count {
let slot_id = self.get_key(index).to_slot_id();
map.insert(slot_id, index);
}
*cache = Some(map);
}
pub fn item_count(&self) -> usize {
self.total_count
}
pub fn intervals(&self) -> &[LazyListInterval] {
&self.intervals
}
pub fn get_key(&self, index: usize) -> LazyLayoutKey {
if let Some((interval, local_index)) = self.find_interval(index)
&& let Some(key_fn) = &interval.key
{
return LazyLayoutKey::User(key_fn(local_index));
}
LazyLayoutKey::Index(index)
}
pub fn get_content_type(&self, index: usize) -> Option<u64> {
if let Some((interval, local_index)) = self.find_interval(index)
&& let Some(type_fn) = &interval.content_type
{
return Some(type_fn(local_index));
}
None
}
pub fn invoke_content(&self, index: usize) {
if let Some((interval, local_index)) = self.find_interval(index) {
(interval.content)(local_index);
}
}
pub fn with_interval<T, F>(&self, global_index: usize, block: F) -> Option<T>
where
F: FnOnce(usize, &LazyListInterval) -> T,
{
self.find_interval(global_index)
.map(|(interval, local_index)| block(local_index, interval))
}
#[must_use]
pub fn get_index_by_key(&self, key: LazyLayoutKey) -> Option<usize> {
let slot_id = key.to_slot_id();
self.get_index_by_slot_id(slot_id)
}
pub fn get_index_by_key_in_range(
&self,
key: LazyLayoutKey,
range: std::ops::Range<usize>,
) -> Option<usize> {
let start = range.start.min(self.total_count);
let end = range.end.min(self.total_count);
(start..end).find(|&index| self.get_key(index) == key)
}
const CACHE_THRESHOLD: usize = 64;
#[must_use]
pub fn get_index_by_slot_id(&self, slot_id: u64) -> Option<usize> {
if self.total_count <= Self::CACHE_THRESHOLD {
return (0..self.total_count)
.find(|&index| self.get_key(index).to_slot_id() == slot_id);
}
self.ensure_cache();
if let Some(cache) = self.key_cache.borrow().as_ref() {
return cache.get(&slot_id).copied();
}
log::warn!(
"get_index_by_slot_id: cache unexpectedly missing ({} items), using linear search",
self.total_count
);
(0..self.total_count).find(|&index| self.get_key(index).to_slot_id() == slot_id)
}
pub fn get_index_by_slot_id_in_range(
&self,
slot_id: u64,
range: std::ops::Range<usize>,
) -> Option<usize> {
let start = range.start.min(self.total_count);
let end = range.end.min(self.total_count);
(start..end).find(|&index| self.get_key(index).to_slot_id() == slot_id)
}
fn find_interval(&self, index: usize) -> Option<(&LazyListInterval, usize)> {
if self.intervals.is_empty() || index >= self.total_count {
return None;
}
let pos = self
.intervals
.partition_point(|interval| interval.start_index + interval.count <= index);
if pos < self.intervals.len() {
let interval = &self.intervals[pos];
if index >= interval.start_index && index < interval.start_index + interval.count {
let local_index = index - interval.start_index;
return Some((interval, local_index));
}
}
None
}
}
impl Default for LazyListIntervalContent {
fn default() -> Self {
Self::new()
}
}
impl LazyListScope for LazyListIntervalContent {
fn item_keyed<F>(&mut self, key: Option<u64>, content_type: Option<u64>, content: F)
where
F: Fn() + 'static,
{
self.invalidate_cache();
let start_index = self.total_count;
self.intervals.push(LazyListInterval {
start_index,
count: 1,
key: key.map(|k| Rc::new(move |_| k) as Rc<dyn Fn(usize) -> u64>),
content_type: content_type.map(|t| Rc::new(move |_| t) as Rc<dyn Fn(usize) -> u64>),
content: Rc::new(move |_| content()),
});
self.total_count += 1;
}
fn items<I, F>(&mut self, items: I, item_content: F)
where
I: Into<LazyItems>,
F: Fn(usize) + 'static,
{
let items = items.into();
let count = items.count();
if count == 0 {
return;
}
self.invalidate_cache();
let start_index = self.total_count;
self.intervals.push(LazyListInterval {
start_index,
count,
key: items.key_fn(),
content_type: items.content_type_fn(),
content: Rc::new(item_content),
});
self.total_count += count;
}
}
use crate::lazy::item_provider::LazyLayoutItemProvider;
impl LazyLayoutItemProvider for LazyListIntervalContent {
fn item_count(&self) -> usize {
self.total_count
}
fn get_key(&self, index: usize) -> u64 {
LazyListIntervalContent::get_key(self, index).to_slot_id()
}
fn get_content_type(&self, index: usize) -> Option<u64> {
LazyListIntervalContent::get_content_type(self, index)
}
fn get_index(&self, key: u64) -> Option<usize> {
self.get_index_by_slot_id(key)
}
}
pub trait LazyListScopeExt: LazyListScope {
fn items_slice<T, F>(&mut self, items: &[T], item_content: F)
where
T: Clone + 'static,
F: Fn(&T) + 'static,
{
let items_rc: Rc<[T]> = items.to_vec().into();
self.items(items.len(), move |index| {
if let Some(item) = items_rc.get(index) {
item_content(item);
}
});
}
fn items_vec<T, F>(&mut self, items: Vec<T>, item_content: F)
where
T: 'static,
F: Fn(&T) + 'static,
{
let len = items.len();
let items_rc: Rc<[T]> = Rc::from(items);
self.items(len, move |index| {
if let Some(item) = items_rc.get(index) {
item_content(item);
}
});
}
fn items_indexed<T, L, F>(&mut self, items: L, item_content: F)
where
T: 'static,
L: Into<Rc<[T]>>,
F: Fn(usize, &T) + 'static,
{
let items_rc: Rc<[T]> = items.into();
self.items(items_rc.len(), move |index| {
if let Some(item) = items_rc.get(index) {
item_content(index, item);
}
});
}
fn items_slice_rc<T, F>(&mut self, items: Rc<[T]>, item_content: F)
where
T: 'static,
F: Fn(&T) + 'static,
{
let len = items.len();
self.items(len, move |index| {
if let Some(item) = items.get(index) {
item_content(item);
}
});
}
fn items_indexed_rc<T, F>(&mut self, items: Rc<[T]>, item_content: F)
where
T: 'static,
F: Fn(usize, &T) + 'static,
{
let len = items.len();
self.items(len, move |index| {
if let Some(item) = items.get(index) {
item_content(index, item);
}
});
}
fn items_with_provider<T, P, F>(&mut self, count: usize, provider: P, item_content: F)
where
T: 'static,
P: Fn(usize) -> Option<T> + 'static,
F: Fn(T) + 'static,
{
self.items(count, move |index| {
if let Some(item) = provider(index) {
item_content(item);
}
});
}
fn items_indexed_with_provider<T, P, F>(&mut self, count: usize, provider: P, item_content: F)
where
T: 'static,
P: Fn(usize) -> Option<T> + 'static,
F: Fn(usize, T) + 'static,
{
self.items(count, move |index| {
if let Some(item) = provider(index) {
item_content(index, item);
}
});
}
}
impl<T: LazyListScope + ?Sized> LazyListScopeExt for T {}
#[cfg(test)]
#[path = "tests/lazy_list_scope_tests.rs"]
mod tests;