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 {} key {:#018x} exceeds 62 bits; mixing to 62 bits to avoid overflow",
kind,
value
);
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)]
mod tests {
use std::cell::Cell;
use super::*;
#[test]
fn key_overflow_warning_suppression_has_no_process_global_state() {
let source = include_str!("lazy_list_scope.rs");
let user_logged = ["USER_OVERFLOW", "_LOGGED"].concat();
let index_logged = ["INDEX_OVERFLOW", "_LOGGED"].concat();
let atomic_bool = ["Atomic", "Bool"].concat();
assert!(
!source.contains(&user_logged)
&& !source.contains(&index_logged)
&& !source.contains(&atomic_bool),
"lazy-list key overflow diagnostics must not use process-global suppression state"
);
}
#[test]
fn test_single_item() {
let mut content = LazyListIntervalContent::new();
let called = Rc::new(Cell::new(false));
let called_clone = Rc::clone(&called);
content.item_keyed(Some(42), None, move || {
called_clone.set(true);
});
assert_eq!(content.item_count(), 1);
assert_eq!(content.get_key(0), LazyLayoutKey::User(42));
content.invoke_content(0);
assert!(called.get());
}
#[test]
fn test_multiple_items() {
let mut content = LazyListIntervalContent::new();
content.items(LazyItems::new(5).key(|i| (i * 10) as u64), |_i| {});
assert_eq!(content.item_count(), 5);
assert_eq!(content.get_key(0), LazyLayoutKey::User(0));
assert_eq!(content.get_key(1), LazyLayoutKey::User(10));
assert_eq!(content.get_key(4), LazyLayoutKey::User(40));
}
#[test]
fn test_mixed_intervals() {
let mut content = LazyListIntervalContent::new();
content.item_keyed(Some(100), None, || {});
content.items(LazyItems::new(3).key(|i| i as u64), |_| {});
content.item_keyed(Some(200), None, || {});
assert_eq!(content.item_count(), 5);
assert_eq!(content.get_key(0), LazyLayoutKey::User(100)); assert_eq!(content.get_key(1), LazyLayoutKey::User(0)); assert_eq!(content.get_key(2), LazyLayoutKey::User(1)); assert_eq!(content.get_key(3), LazyLayoutKey::User(2)); assert_eq!(content.get_key(4), LazyLayoutKey::User(200)); }
#[test]
fn test_with_interval() {
let mut content = LazyListIntervalContent::new();
content.items(5, |_| {});
let result = content.with_interval(3, |local_idx, interval| (local_idx, interval.count));
assert_eq!(result, Some((3, 5)));
}
#[test]
fn test_user_keys_dont_collide_with_default_keys() {
let mut content = LazyListIntervalContent::new();
content.item_keyed(Some(0), None, || {});
content.item(|| {});
content.item_keyed(Some(1), None, || {});
assert_eq!(content.get_key(0), LazyLayoutKey::User(0));
assert_eq!(content.get_key(1), LazyLayoutKey::Index(1));
assert_eq!(content.get_key(2), LazyLayoutKey::User(1));
assert_ne!(content.get_key(0), content.get_key(1));
assert_ne!(content.get_key(2), content.get_key(1));
assert_ne!(
content.get_key(0).to_slot_id(),
content.get_key(1).to_slot_id()
);
}
#[test]
fn test_slot_id_collision_prevention() {
let user_key = LazyLayoutKey::User(0);
let index_key = LazyLayoutKey::Index(0);
assert_ne!(user_key.to_slot_id(), index_key.to_slot_id());
assert_eq!(user_key.to_slot_id(), 0); assert_eq!(index_key.to_slot_id(), 1u64 << 62);
assert!(user_key.to_slot_id() < (1u64 << 62));
assert!(index_key.to_slot_id() >= (1u64 << 62));
assert!(index_key.to_slot_id() < (2u64 << 62));
let user_max = LazyLayoutKey::User((1u64 << 62) - 1);
assert!(
user_max.to_slot_id() < (1u64 << 62),
"User keys stay in user range"
);
assert_eq!(user_max.to_slot_id(), (1u64 << 62) - 1);
let index_large = LazyLayoutKey::Index(((1u64 << 62) - 1) as usize);
assert!(
index_large.to_slot_id() >= (1u64 << 62),
"Index keys stay in index range"
);
assert!(
index_large.to_slot_id() < (2u64 << 62),
"Index keys below reserved range"
);
}
#[test]
fn test_user_key_overflow_is_stable_and_tagged() {
let user_max = LazyLayoutKey::User(u64::MAX);
let slot = user_max.to_slot_id();
assert_eq!(slot, user_max.to_slot_id());
assert!(slot < (1u64 << 62));
}
#[test]
fn test_index_key_overflow_is_stable_and_tagged() {
let index_max = LazyLayoutKey::Index(usize::MAX);
let slot = index_max.to_slot_id();
assert_eq!(slot, index_max.to_slot_id());
assert!(slot >= (1u64 << 62));
assert!(slot < (2u64 << 62));
}
#[test]
fn test_user_key_high_bits_influence_slot_id() {
let key_low = LazyLayoutKey::User(0x0000_0000_0000_0001);
let key_high = LazyLayoutKey::User(0x4000_0000_0000_0001); assert_ne!(
key_low.to_slot_id(),
key_high.to_slot_id(),
"High bits are mixed into the slot id to avoid truncation collisions"
);
}
#[test]
fn test_items_slice() {
let mut content = LazyListIntervalContent::new();
let data = vec!["Apple", "Banana", "Cherry"];
let items_visited = Rc::new(RefCell::new(Vec::new()));
let items_clone = items_visited.clone();
content.items_slice(&data, move |item: &&str| {
items_clone.borrow_mut().push((*item).to_string());
});
assert_eq!(content.item_count(), 3);
for i in 0..3 {
content.invoke_content(i);
}
let visited = items_visited.borrow();
assert_eq!(*visited, vec!["Apple", "Banana", "Cherry"]);
}
#[test]
fn test_items_indexed() {
let mut content = LazyListIntervalContent::new();
let data = vec![
"Apple".to_string(),
"Banana".to_string(),
"Cherry".to_string(),
];
let items_visited = Rc::new(RefCell::new(Vec::new()));
let items_clone = items_visited.clone();
content.items_indexed(data, move |index, item: &String| {
items_clone.borrow_mut().push((index, item.clone()));
});
assert_eq!(content.item_count(), 3);
for i in 0..3 {
content.invoke_content(i);
}
let visited = items_visited.borrow();
assert_eq!(
*visited,
vec![
(0, "Apple".to_string()),
(1, "Banana".to_string()),
(2, "Cherry".to_string())
]
);
}
#[test]
fn test_items_indexed_slice() {
let mut content = LazyListIntervalContent::new();
let data = vec!["Apple", "Banana", "Cherry"];
let items_visited = Rc::new(RefCell::new(Vec::new()));
let items_clone = items_visited.clone();
content.items_indexed(data.as_slice(), move |index, item: &&str| {
items_clone.borrow_mut().push((index, (*item).to_string()));
});
assert_eq!(content.item_count(), 3);
for i in 0..3 {
content.invoke_content(i);
}
let visited = items_visited.borrow();
assert_eq!(
*visited,
vec![
(0, "Apple".to_string()),
(1, "Banana".to_string()),
(2, "Cherry".to_string())
]
);
}
#[test]
fn test_items_slice_rc() {
let mut content = LazyListIntervalContent::new();
let data: Rc<[String]> = Rc::from(vec!["Apple".into(), "Banana".into()]);
let items_visited = Rc::new(RefCell::new(Vec::new()));
let items_clone = items_visited.clone();
content.items_slice_rc(Rc::clone(&data), move |item: &String| {
items_clone.borrow_mut().push(item.clone());
});
assert_eq!(content.item_count(), 2);
for i in 0..2 {
content.invoke_content(i);
}
let visited = items_visited.borrow();
assert_eq!(*visited, vec!["Apple", "Banana"]);
}
#[test]
fn test_items_indexed_rc() {
let mut content = LazyListIntervalContent::new();
let data: Rc<[String]> = Rc::from(vec!["Apple".into(), "Banana".into()]);
let items_visited = Rc::new(RefCell::new(Vec::new()));
let items_clone = items_visited.clone();
content.items_indexed_rc(Rc::clone(&data), move |index, item: &String| {
items_clone.borrow_mut().push((index, item.clone()));
});
assert_eq!(content.item_count(), 2);
for i in 0..2 {
content.invoke_content(i);
}
let visited = items_visited.borrow();
assert_eq!(
*visited,
vec![(0, "Apple".to_string()), (1, "Banana".to_string())]
);
}
#[test]
fn test_items_with_provider() {
let mut content = LazyListIntervalContent::new();
let data = ["Apple", "Banana", "Cherry"];
let items_visited = Rc::new(RefCell::new(Vec::new()));
let items_clone = items_visited.clone();
content.items_with_provider(
data.len(),
move |index| data.get(index).copied(),
move |item: &str| {
items_clone.borrow_mut().push(item.to_string());
},
);
assert_eq!(content.item_count(), 3);
for i in 0..3 {
content.invoke_content(i);
}
let visited = items_visited.borrow();
assert_eq!(*visited, vec!["Apple", "Banana", "Cherry"]);
}
#[test]
fn test_items_indexed_with_provider() {
let mut content = LazyListIntervalContent::new();
let data = ["Apple", "Banana", "Cherry"];
let items_visited = Rc::new(RefCell::new(Vec::new()));
let items_clone = items_visited.clone();
content.items_indexed_with_provider(
data.len(),
move |index| data.get(index).copied(),
move |index, item: &str| {
items_clone.borrow_mut().push((index, item.to_string()));
},
);
assert_eq!(content.item_count(), 3);
for i in 0..3 {
content.invoke_content(i);
}
let visited = items_visited.borrow();
assert_eq!(
*visited,
vec![
(0, "Apple".to_string()),
(1, "Banana".to_string()),
(2, "Cherry".to_string())
]
);
}
#[test]
fn test_large_list_cache_works() {
let mut content = LazyListIntervalContent::new();
content.items(
LazyItems::new(20_000).key(|i| (i * 7) as u64),
|_| {},
);
let key_19999 = content.get_key(19999);
assert_eq!(key_19999, LazyLayoutKey::User(19999 * 7));
let slot_id = key_19999.to_slot_id();
let found_index = content.get_index_by_slot_id(slot_id);
assert_eq!(found_index, Some(19999));
let key_10000 = content.get_key(10000);
let slot_id_mid = key_10000.to_slot_id();
let found_mid = content.get_index_by_slot_id(slot_id_mid);
assert_eq!(found_mid, Some(10000));
}
}