use std::collections::HashMap;
use std::hash::Hash;
use std::sync::{Arc, Mutex, MutexGuard};
use crate::{Source, ThreadSafeContext, ThreadSafeSourceMap};
pub struct ThreadSafeSourceTree<Id, V> {
inner: Arc<ThreadSafeSourceTreeNode<Id, V>>,
}
struct ThreadSafeSourceTreeNode<Id, V> {
id: Id,
value: Source<V>,
order: ThreadSafeSourceMap<Id, ()>,
nodes: Mutex<HashMap<Id, ThreadSafeSourceTree<Id, V>>>,
}
impl<Id, V> Clone for ThreadSafeSourceTree<Id, V> {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
impl<Id, V> ThreadSafeSourceTree<Id, V>
where
Id: Eq + Hash + Clone + Send + Sync + 'static,
V: PartialEq + Clone + Send + Sync + 'static,
{
pub fn leaf(ctx: &ThreadSafeContext, id: Id, value: V) -> Self {
Self {
inner: Arc::new(ThreadSafeSourceTreeNode {
id,
value: ctx.source(value),
order: ThreadSafeSourceMap::new(ctx),
nodes: Mutex::new(HashMap::new()),
}),
}
}
fn nodes(&self) -> MutexGuard<'_, HashMap<Id, ThreadSafeSourceTree<Id, V>>> {
self.inner
.nodes
.lock()
.expect("thread-safe source-tree nodes mutex poisoned")
}
pub fn id(&self) -> &Id {
&self.inner.id
}
pub fn value(&self) -> Source<V> {
self.inner.value
}
pub fn get(&self, ctx: &ThreadSafeContext) -> V {
ctx.get(&self.inner.value)
}
pub fn set(&self, ctx: &ThreadSafeContext, value: V) {
ctx.set(&self.inner.value, value);
}
pub fn insert_child(
&self,
ctx: &ThreadSafeContext,
id: Id,
value: V,
) -> ThreadSafeSourceTree<Id, V> {
if let Some(existing) = self.nodes().get(&id).cloned() {
return existing;
}
let candidate = ThreadSafeSourceTree::leaf(ctx, id.clone(), value);
let child = {
let mut nodes = self.nodes();
if let Some(existing) = nodes.get(&id) {
return existing.clone();
}
nodes.insert(id.clone(), candidate.clone());
candidate
};
self.inner.order.set(ctx, id, ());
child
}
pub fn attach_child(
&self,
ctx: &ThreadSafeContext,
child: ThreadSafeSourceTree<Id, V>,
) -> ThreadSafeSourceTree<Id, V> {
let id = child.id().clone();
{
let mut nodes = self.nodes();
if let Some(existing) = nodes.get(&id) {
return existing.clone();
}
nodes.insert(id.clone(), child.clone());
}
self.inner.order.set(ctx, id, ());
child
}
pub fn child(&self, id: &Id) -> Option<ThreadSafeSourceTree<Id, V>> {
self.nodes().get(id).cloned()
}
pub fn remove_child(&self, ctx: &ThreadSafeContext, id: &Id) -> bool {
let removed = self.nodes().remove(id).is_some();
if removed {
self.inner.order.remove(ctx, id);
}
removed
}
pub fn move_child(&self, ctx: &ThreadSafeContext, id: &Id, index: usize) -> bool {
self.inner.order.move_to(ctx, id, index)
}
pub fn move_child_before(&self, ctx: &ThreadSafeContext, id: &Id, anchor: &Id) -> bool {
self.inner.order.move_before(ctx, id, anchor)
}
pub fn move_child_after(&self, ctx: &ThreadSafeContext, id: &Id, anchor: &Id) -> bool {
self.inner.order.move_after(ctx, id, anchor)
}
pub fn child_ids(&self, ctx: &ThreadSafeContext) -> Vec<Id> {
self.inner.order.keys(ctx)
}
pub fn children(&self, ctx: &ThreadSafeContext) -> Vec<ThreadSafeSourceTree<Id, V>> {
let ids = self.inner.order.keys(ctx);
let nodes = self.nodes();
ids.into_iter()
.filter_map(|id| nodes.get(&id).cloned())
.collect()
}
pub fn len(&self, ctx: &ThreadSafeContext) -> usize {
self.inner.order.len(ctx)
}
pub fn is_empty(&self, ctx: &ThreadSafeContext) -> bool {
self.inner.order.is_empty(ctx)
}
pub fn contains_child(&self, ctx: &ThreadSafeContext, id: &Id) -> bool {
self.inner.order.contains_key(ctx, id)
}
pub fn resolve_path(&self, path: &[Id]) -> Option<ThreadSafeSourceTree<Id, V>> {
let mut node = self.clone();
for segment in path {
node = node.child(segment)?;
}
Some(node)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_send_sync<T: Send + Sync>() {}
#[test]
fn tree_is_send_sync() {
assert_send_sync::<ThreadSafeSourceTree<String, String>>();
}
#[test]
fn ordered_children_preserve_identity_and_value_isolation() {
let ctx = ThreadSafeContext::new();
let root = ThreadSafeSourceTree::leaf(&ctx, "root", 0);
let a = root.insert_child(&ctx, "a", 1);
root.insert_child(&ctx, "b", 2);
root.insert_child(&ctx, "c", 3);
let observed_a = {
let a = a.clone();
let runs = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let counted = Arc::clone(&runs);
let slot = ctx.computed(move |ctx| {
counted.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
a.get(ctx)
});
(slot, runs)
};
assert_eq!(ctx.get(&observed_a.0), 1);
root.child(&"b").unwrap().set(&ctx, 20);
root.move_child(&ctx, &"c", 0);
assert_eq!(root.child_ids(&ctx), vec!["c", "a", "b"]);
assert_eq!(ctx.get(&observed_a.0), 1);
assert_eq!(observed_a.1.load(std::sync::atomic::Ordering::SeqCst), 1);
assert_eq!(root.child(&"a").unwrap().value(), a.value());
}
}