use std::cell::{Cell as StdCell, RefCell};
use std::collections::HashMap;
use std::hash::Hash;
use std::rc::Rc;
use crate::Context;
use crate::cell::CellHandle;
pub struct CellMap<K, V> {
inner: Rc<CellMapInner<K, V>>,
}
struct CellMapInner<K, V> {
entries: RefCell<HashMap<K, CellHandle<V>>>,
order: RefCell<Vec<K>>,
membership: CellHandle<u64>,
version: StdCell<u64>,
}
impl<K, V> Clone for CellMap<K, V> {
fn clone(&self) -> Self {
Self {
inner: Rc::clone(&self.inner),
}
}
}
impl<K, V> CellMap<K, V>
where
K: Eq + Hash + Clone + 'static,
V: PartialEq + Clone + 'static,
{
pub fn new(ctx: &Context) -> Self {
Self {
inner: Rc::new(CellMapInner {
entries: RefCell::new(HashMap::new()),
order: RefCell::new(Vec::new()),
membership: ctx.cell(0u64),
version: StdCell::new(0),
}),
}
}
fn bump_membership(&self, ctx: &Context) {
let next = self.inner.version.get().wrapping_add(1);
self.inner.version.set(next);
ctx.set_cell(&self.inner.membership, next);
}
pub fn entry_with(&self, ctx: &Context, key: K, default: impl FnOnce() -> V) -> CellHandle<V> {
if let Some(handle) = self.inner.entries.borrow().get(&key) {
return *handle;
}
let handle = ctx.cell(default());
self.inner.entries.borrow_mut().insert(key.clone(), handle);
self.inner.order.borrow_mut().push(key);
self.bump_membership(ctx);
handle
}
pub fn entry(&self, ctx: &Context, key: K, default: V) -> CellHandle<V> {
self.entry_with(ctx, key, || default)
}
pub fn handle(&self, key: &K) -> Option<CellHandle<V>> {
self.inner.entries.borrow().get(key).copied()
}
pub fn get(&self, ctx: &Context, key: &K) -> Option<V> {
let handle = self.inner.entries.borrow().get(key).copied();
handle.map(|h| ctx.get_cell(&h))
}
pub fn set(&self, ctx: &Context, key: K, value: V) {
if let Some(handle) = self.inner.entries.borrow().get(&key).copied() {
handle.set(ctx, value);
return;
}
self.entry_with(ctx, key, || value);
}
pub fn remove(&self, ctx: &Context, key: &K) -> bool {
let removed = self.inner.entries.borrow_mut().remove(key);
let Some(handle) = removed else {
return false;
};
self.inner.order.borrow_mut().retain(|k| k != key);
handle.clear_dependents(ctx);
self.bump_membership(ctx);
true
}
pub fn keys(&self, ctx: &Context) -> Vec<K> {
let _ = ctx.get_cell(&self.inner.membership);
self.inner.order.borrow().clone()
}
pub fn len(&self, ctx: &Context) -> usize {
let _ = ctx.get_cell(&self.inner.membership);
self.inner.order.borrow().len()
}
pub fn is_empty(&self, ctx: &Context) -> bool {
self.len(ctx) == 0
}
pub fn contains_key(&self, ctx: &Context, key: &K) -> bool {
let _ = ctx.get_cell(&self.inner.membership);
self.inner.entries.borrow().contains_key(key)
}
pub fn len_untracked(&self) -> usize {
self.inner.order.borrow().len()
}
}
pub struct CellFamily<K, V> {
map: CellMap<K, V>,
factory: Rc<dyn Fn(&K) -> V>,
}
impl<K, V> Clone for CellFamily<K, V> {
fn clone(&self) -> Self {
Self {
map: self.map.clone(),
factory: Rc::clone(&self.factory),
}
}
}
impl<K, V> CellFamily<K, V>
where
K: Eq + Hash + Clone + 'static,
V: PartialEq + Clone + 'static,
{
pub fn new(ctx: &Context, factory: impl Fn(&K) -> V + 'static) -> Self {
Self {
map: CellMap::new(ctx),
factory: Rc::new(factory),
}
}
pub fn get(&self, ctx: &Context, key: K) -> CellHandle<V> {
let factory = Rc::clone(&self.factory);
let k = key.clone();
self.map.entry_with(ctx, key, move || factory(&k))
}
pub fn map(&self) -> &CellMap<K, V> {
&self.map
}
pub fn remove(&self, ctx: &Context, key: &K) -> bool {
self.map.remove(ctx, key)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn entry_caches_one_cell_per_key() {
let ctx = Context::new();
let map: CellMap<&str, i32> = CellMap::new(&ctx);
let a1 = map.entry(&ctx, "a", 1);
let a2 = map.entry(&ctx, "a", 999);
assert_eq!(a1.id, a2.id);
assert_eq!(a1.get(&ctx), 1);
assert_eq!(map.len_untracked(), 1);
}
#[test]
fn membership_is_reactive_but_value_changes_are_not() {
let ctx = Context::new();
let map: CellMap<&str, i32> = CellMap::new(&ctx);
let a = map.entry(&ctx, "a", 1);
map.entry(&ctx, "b", 2);
let count = ctx.computed({
let map = map.clone();
move |ctx| map.len(ctx)
});
assert_eq!(ctx.get(&count), 2);
a.set(&ctx, 100);
assert!(ctx.is_set(&count), "membership reader stayed cached");
assert_eq!(ctx.get(&count), 2);
map.entry(&ctx, "c", 3);
assert_eq!(ctx.get(&count), 3);
assert!(map.remove(&ctx, &"b"));
assert_eq!(ctx.get(&count), 2);
assert_eq!(map.keys(&ctx), vec!["a", "c"]);
}
#[test]
fn per_entry_reads_are_independent() {
let ctx = Context::new();
let map: CellMap<&str, i32> = CellMap::new(&ctx);
let a = map.entry(&ctx, "a", 1);
let b = map.entry(&ctx, "b", 2);
let view_a = ctx.computed({
let map = map.clone();
move |ctx| map.get(ctx, &"a").unwrap_or(0) * 10
});
assert_eq!(ctx.get(&view_a), 10);
b.set(&ctx, 222);
assert!(ctx.is_set(&view_a), "sibling change must not invalidate");
assert_eq!(ctx.get(&view_a), 10);
a.set(&ctx, 5);
assert_eq!(ctx.get(&view_a), 50);
}
#[test]
fn family_mints_via_factory_and_caches() {
let ctx = Context::new();
let fam: CellFamily<u32, u32> = CellFamily::new(&ctx, |&k| k * 2);
let c7 = fam.get(&ctx, 7);
assert_eq!(c7.get(&ctx), 14);
c7.set(&ctx, 100);
assert_eq!(fam.get(&ctx, 7).get(&ctx), 100);
assert_eq!(fam.map().len_untracked(), 1);
}
#[test]
fn contains_key_tracks_membership() {
let ctx = Context::new();
let map: CellMap<i32, i32> = CellMap::new(&ctx);
let has_5 = ctx.computed({
let map = map.clone();
move |ctx| map.contains_key(ctx, &5)
});
assert!(!ctx.get(&has_5));
map.entry(&ctx, 5, 50);
assert!(ctx.get(&has_5));
}
}