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>,
order_signal: CellHandle<u64>,
order_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),
order_signal: ctx.cell(0u64),
order_version: StdCell::new(0),
}),
}
}
fn bump_order(&self, ctx: &Context) {
let next = self.inner.order_version.get().wrapping_add(1);
self.inner.order_version.set(next);
ctx.set_cell(&self.inner.order_signal, next);
}
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);
self.bump_order(ctx);
}
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.order_signal);
self.inner.order.borrow().clone()
}
pub fn position(&self, key: &K) -> Option<usize> {
self.inner.order.borrow().iter().position(|k| k == key)
}
pub fn move_to(&self, ctx: &Context, key: &K, index: usize) -> bool {
let mut order = self.inner.order.borrow_mut();
let Some(from) = order.iter().position(|k| k == key) else {
return false;
};
let to = index.min(order.len().saturating_sub(1));
if from == to {
return true; }
let k = order.remove(from);
order.insert(to, k);
drop(order);
self.bump_order(ctx);
true
}
pub fn move_before(&self, ctx: &Context, key: &K, anchor: &K) -> bool {
let Some(anchor_idx) = self.position(anchor) else {
return false;
};
let from = match self.position(key) {
Some(i) => i,
None => return false,
};
let target = if from < anchor_idx {
anchor_idx - 1
} else {
anchor_idx
};
self.move_to(ctx, key, target)
}
pub fn move_after(&self, ctx: &Context, key: &K, anchor: &K) -> bool {
let Some(anchor_idx) = self.position(anchor) else {
return false;
};
let from = match self.position(key) {
Some(i) => i,
None => return false,
};
let target = if from <= anchor_idx {
anchor_idx
} else {
anchor_idx + 1
};
self.move_to(ctx, key, target)
}
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 move_to_reorders_keys_and_keeps_cell_identity() {
let ctx = Context::new();
let map: CellMap<&str, i32> = CellMap::new(&ctx);
let a = map.entry(&ctx, "a", 1);
map.entry(&ctx, "b", 2);
map.entry(&ctx, "c", 3);
assert_eq!(map.keys(&ctx), vec!["a", "b", "c"]);
assert!(map.move_to(&ctx, &"c", 0));
assert_eq!(map.keys(&ctx), vec!["c", "a", "b"]);
assert_eq!(map.handle(&"a").unwrap().id, a.id);
assert_eq!(map.get(&ctx, &"a"), Some(1));
assert_eq!(map.get(&ctx, &"c"), Some(3));
assert!(!map.move_to(&ctx, &"z", 0));
assert_eq!(map.keys(&ctx), vec!["c", "a", "b"]);
}
#[test]
fn pure_move_invalidates_order_but_not_membership_readers() {
let ctx = Context::new();
let map: CellMap<&str, i32> = CellMap::new(&ctx);
map.entry(&ctx, "a", 1);
map.entry(&ctx, "b", 2);
map.entry(&ctx, "c", 3);
let order_reader = ctx.computed({
let map = map.clone();
move |ctx| map.keys(ctx).join(",")
});
let count = ctx.computed({
let map = map.clone();
move |ctx| map.len(ctx)
});
let has_b = ctx.computed({
let map = map.clone();
move |ctx| map.contains_key(ctx, &"b")
});
assert_eq!(ctx.get(&order_reader), "a,b,c");
assert_eq!(ctx.get(&count), 3);
assert!(ctx.get(&has_b));
assert!(map.move_to(&ctx, &"a", 2));
assert_eq!(ctx.get(&order_reader), "b,c,a");
assert!(
ctx.is_set(&count),
"len reader must stay cached on pure move"
);
assert!(
ctx.is_set(&has_b),
"contains_key reader must stay cached on pure move"
);
assert_eq!(ctx.get(&count), 3);
}
#[test]
fn move_to_is_noop_when_position_unchanged() {
let ctx = Context::new();
let map: CellMap<&str, i32> = CellMap::new(&ctx);
map.entry(&ctx, "a", 1);
map.entry(&ctx, "b", 2);
let order_reader = ctx.computed({
let map = map.clone();
move |ctx| map.keys(ctx).join(",")
});
assert_eq!(ctx.get(&order_reader), "a,b");
assert!(map.move_to(&ctx, &"a", 0));
assert!(
ctx.is_set(&order_reader),
"no-op move must not invalidate keys readers"
);
assert!(map.move_to(&ctx, &"a", 99));
assert_eq!(ctx.get(&order_reader), "b,a");
}
#[test]
fn move_before_and_after_place_relative_to_anchor() {
let ctx = Context::new();
let map: CellMap<i32, i32> = CellMap::new(&ctx);
for k in 0..4 {
map.entry(&ctx, k, k * 10);
}
assert_eq!(map.keys(&ctx), vec![0, 1, 2, 3]);
assert!(map.move_before(&ctx, &3, &1));
assert_eq!(map.keys(&ctx), vec![0, 3, 1, 2]);
assert!(map.move_after(&ctx, &0, &2));
assert_eq!(map.keys(&ctx), vec![3, 1, 2, 0]);
assert!(!map.move_before(&ctx, &3, &99));
assert!(!map.move_after(&ctx, &99, &2));
}
#[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));
}
}