use std::cell::RefCell;
use std::collections::HashMap;
use std::hash::Hash;
use std::rc::Rc;
use crate::Context;
use crate::cell::CellHandle;
use crate::cell_family::CellMap;
pub struct CellTree<Id, V> {
inner: Rc<CellTreeNode<Id, V>>,
}
struct CellTreeNode<Id, V> {
id: Id,
value: CellHandle<V>,
order: CellMap<Id, ()>,
nodes: RefCell<HashMap<Id, CellTree<Id, V>>>,
}
impl<Id, V> Clone for CellTree<Id, V> {
fn clone(&self) -> Self {
Self {
inner: Rc::clone(&self.inner),
}
}
}
impl<Id, V> CellTree<Id, V>
where
Id: Eq + Hash + Clone + 'static,
V: PartialEq + Clone + 'static,
{
pub fn leaf(ctx: &Context, id: Id, value: V) -> Self {
Self {
inner: Rc::new(CellTreeNode {
id,
value: ctx.cell(value),
order: CellMap::new(ctx),
nodes: RefCell::new(HashMap::new()),
}),
}
}
pub fn id(&self) -> &Id {
&self.inner.id
}
pub fn value(&self) -> CellHandle<V> {
self.inner.value
}
pub fn get(&self, ctx: &Context) -> V {
ctx.get_cell(&self.inner.value)
}
pub fn set(&self, ctx: &Context, value: V) {
ctx.set_cell(&self.inner.value, value);
}
pub fn insert_child(&self, ctx: &Context, id: Id, value: V) -> CellTree<Id, V> {
if let Some(existing) = self.inner.nodes.borrow().get(&id) {
return existing.clone();
}
let child = CellTree::leaf(ctx, id.clone(), value);
self.inner
.nodes
.borrow_mut()
.insert(id.clone(), child.clone());
self.inner.order.entry(ctx, id, ());
child
}
pub fn attach_child(&self, ctx: &Context, child: CellTree<Id, V>) -> CellTree<Id, V> {
let id = child.inner.id.clone();
if let Some(existing) = self.inner.nodes.borrow().get(&id) {
return existing.clone();
}
self.inner
.nodes
.borrow_mut()
.insert(id.clone(), child.clone());
self.inner.order.entry(ctx, id, ());
child
}
pub fn child(&self, id: &Id) -> Option<CellTree<Id, V>> {
self.inner.nodes.borrow().get(id).cloned()
}
pub fn remove_child(&self, ctx: &Context, id: &Id) -> bool {
let removed = self.inner.nodes.borrow_mut().remove(id).is_some();
if removed {
self.inner.order.remove(ctx, id);
}
removed
}
pub fn move_child(&self, ctx: &Context, id: &Id, index: usize) -> bool {
self.inner.order.move_to(ctx, id, index)
}
pub fn move_child_before(&self, ctx: &Context, id: &Id, anchor: &Id) -> bool {
self.inner.order.move_before(ctx, id, anchor)
}
pub fn move_child_after(&self, ctx: &Context, id: &Id, anchor: &Id) -> bool {
self.inner.order.move_after(ctx, id, anchor)
}
pub fn child_ids(&self, ctx: &Context) -> Vec<Id> {
self.inner.order.keys(ctx)
}
pub fn children(&self, ctx: &Context) -> Vec<CellTree<Id, V>> {
let nodes = self.inner.nodes.borrow();
self.inner
.order
.keys(ctx)
.into_iter()
.filter_map(|k| nodes.get(&k).cloned())
.collect()
}
pub fn len(&self, ctx: &Context) -> usize {
self.inner.order.len(ctx)
}
pub fn is_empty(&self, ctx: &Context) -> bool {
self.inner.order.is_empty(ctx)
}
pub fn contains_child(&self, ctx: &Context, id: &Id) -> bool {
self.inner.order.contains_key(ctx, id)
}
pub fn resolve_path(&self, path: &[Id]) -> Option<CellTree<Id, V>> {
let mut node = self.clone();
for seg in path {
node = node.child(seg)?;
}
Some(node)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn doc(ctx: &Context) -> CellTree<&'static str, &'static str> {
let root = CellTree::leaf(ctx, "root", "doc");
root.insert_child(ctx, "a", "alpha");
root.insert_child(ctx, "b", "bravo");
root.insert_child(ctx, "c", "charlie");
root
}
#[test]
fn ordered_children_and_value_access() {
let ctx = Context::new();
let root = doc(&ctx);
assert_eq!(root.id(), &"root");
assert_eq!(root.get(&ctx), "doc");
assert_eq!(root.child_ids(&ctx), vec!["a", "b", "c"]);
assert_eq!(root.child(&"b").unwrap().get(&ctx), "bravo");
assert_eq!(root.len(&ctx), 3);
}
#[test]
fn per_node_value_isolation() {
let ctx = Context::new();
let root = doc(&ctx);
let a = root.child(&"a").unwrap();
let b = root.child(&"b").unwrap();
let view_a = ctx.computed({
let a = a.clone();
move |ctx| a.get(ctx).to_uppercase()
});
assert_eq!(ctx.get(&view_a), "ALPHA");
b.set(&ctx, "BRAVO!");
assert!(
ctx.is_set(&view_a),
"sibling value edit must not invalidate"
);
assert_eq!(ctx.get(&view_a), "ALPHA");
a.set(&ctx, "alfa");
assert_eq!(ctx.get(&view_a), "ALFA");
}
#[test]
fn child_membership_reactive_per_level() {
let ctx = Context::new();
let root = doc(&ctx);
let a = root.child(&"a").unwrap();
let root_kids = ctx.computed({
let root = root.clone();
move |ctx| root.child_ids(ctx).join(",")
});
let a_kids = ctx.computed({
let a = a.clone();
move |ctx| a.len(ctx)
});
assert_eq!(ctx.get(&root_kids), "a,b,c");
assert_eq!(ctx.get(&a_kids), 0);
a.insert_child(&ctx, "a1", "alpha-one");
assert!(
ctx.is_set(&root_kids),
"deeper membership change must not invalidate parent level"
);
assert_eq!(ctx.get(&a_kids), 1);
root.insert_child(&ctx, "d", "delta");
assert_eq!(ctx.get(&root_kids), "a,b,c,d");
}
#[test]
fn atomic_move_keeps_identity_and_spares_len_readers() {
let ctx = Context::new();
let root = doc(&ctx);
let a = root.child(&"a").unwrap();
a.insert_child(&ctx, "a1", "alpha-one");
let order = ctx.computed({
let root = root.clone();
move |ctx| root.child_ids(ctx).join(",")
});
let count = ctx.computed({
let root = root.clone();
move |ctx| root.len(ctx)
});
assert_eq!(ctx.get(&order), "a,b,c");
assert_eq!(ctx.get(&count), 3);
assert!(root.move_child(&ctx, &"a", 2));
assert_eq!(ctx.get(&order), "b,c,a");
assert!(
ctx.is_set(&count),
"pure move must not invalidate len reader"
);
let a_again = root.child(&"a").unwrap();
assert_eq!(a_again.child_ids(&ctx), vec!["a1"]);
assert_eq!(a_again.get(&ctx), "alpha");
}
#[test]
fn structural_sharing_via_clone() {
let ctx = Context::new();
let root = doc(&ctx);
let a1 = root.child(&"a").unwrap();
let a2 = root.child(&"a").unwrap();
a1.set(&ctx, "shared");
assert_eq!(a2.get(&ctx), "shared");
let n = root.len(&ctx);
root.attach_child(&ctx, a1.clone());
assert_eq!(root.len(&ctx), n);
}
#[test]
fn resolve_path_walks_segments() {
let ctx = Context::new();
let root = CellTree::leaf(&ctx, "root", "r");
let a = root.insert_child(&ctx, "a", "a");
let b = a.insert_child(&ctx, "b", "b");
b.insert_child(&ctx, "c", "c");
assert_eq!(root.resolve_path(&["a", "b", "c"]).unwrap().get(&ctx), "c");
assert!(root.resolve_path(&["a", "x"]).is_none());
}
}