From bbd9cd8051c9065c76572ee57807974f4f97804e Mon Sep 17 00:00:00 2001 From: Kirpal Grewal Date: Tue, 26 Nov 2024 10:54:46 +0000 Subject: [PATCH] create arena tree --- Cargo.lock | 5 + Cargo.toml | 2 + masonry/Cargo.toml | 1 + masonry/src/contexts.rs | 2 +- masonry/src/lib.rs | 1 - masonry/src/passes/accessibility.rs | 2 +- masonry/src/passes/anim.rs | 2 +- masonry/src/passes/compose.rs | 2 +- masonry/src/passes/mod.rs | 2 +- masonry/src/passes/paint.rs | 2 +- masonry/src/passes/update.rs | 2 +- masonry/src/render_root.rs | 2 +- masonry/src/widget/widget_arena.rs | 2 +- tree_arena/ARCHITECTURE.md | 33 + tree_arena/Cargo.toml | 20 + tree_arena/README.md | 3 + tree_arena/src/lib.rs | 24 + .../src/tree_arena_safe.rs | 256 ++++---- tree_arena/src/tree_arena_unsafe.rs | 612 ++++++++++++++++++ tree_arena/tests/basic_tests.rs | 104 +++ 20 files changed, 943 insertions(+), 136 deletions(-) create mode 100644 tree_arena/ARCHITECTURE.md create mode 100644 tree_arena/Cargo.toml create mode 100644 tree_arena/README.md create mode 100644 tree_arena/src/lib.rs rename masonry/src/tree_arena.rs => tree_arena/src/tree_arena_safe.rs (71%) create mode 100644 tree_arena/src/tree_arena_unsafe.rs create mode 100644 tree_arena/tests/basic_tests.rs diff --git a/Cargo.lock b/Cargo.lock index 9c08eda57..55258aed4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1977,6 +1977,7 @@ dependencies = [ "tracing-subscriber", "tracing-tracy", "tracing_android_trace", + "tree_arena", "vello", "web-time", "wgpu", @@ -3722,6 +3723,10 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "tree_arena" +version = "0.1.0" + [[package]] name = "try-lock" version = "0.2.5" diff --git a/Cargo.toml b/Cargo.toml index 35c16c1e4..6c77e74d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "xilem_web/web_examples/spawn_tasks", "xilem_web/web_examples/svgtoy", "xilem_web/web_examples/svgdraw", + "tree_arena", ] [workspace.package] @@ -105,6 +106,7 @@ clippy.duplicated_attributes = "allow" [workspace.dependencies] masonry = { version = "0.2.0", path = "masonry" } xilem_core = { version = "0.1.0", path = "xilem_core" } +tree_arena = { version = "0.1.0", path = "tree_arena" } vello = "0.3" wgpu = "22.1.0" kurbo = "0.11.1" diff --git a/masonry/Cargo.toml b/masonry/Cargo.toml index e33729d1e..4d17534db 100644 --- a/masonry/Cargo.toml +++ b/masonry/Cargo.toml @@ -39,6 +39,7 @@ vello.workspace = true wgpu.workspace = true parley.workspace = true winit.workspace = true +tree_arena.workspace = true smallvec.workspace = true tracing = { workspace = true, features = ["default"] } image.workspace = true diff --git a/masonry/src/contexts.rs b/masonry/src/contexts.rs index 95bfe0355..80c7adc14 100644 --- a/masonry/src/contexts.rs +++ b/masonry/src/contexts.rs @@ -18,9 +18,9 @@ use crate::passes::layout::run_layout_on; use crate::render_root::{MutateCallback, RenderRootSignal, RenderRootState}; use crate::text::BrushIndex; use crate::theme::get_debug_color; -use crate::tree_arena::{ArenaMutChildren, ArenaRefChildren}; use crate::widget::{WidgetMut, WidgetRef, WidgetState}; use crate::{AllowRawMut, BoxConstraints, Insets, Point, Rect, Size, Widget, WidgetId, WidgetPod}; +use tree_arena::{ArenaMutChildren, ArenaRefChildren}; // Note - Most methods defined in this file revolve around `WidgetState` fields. // Consider reading `WidgetState` documentation (especially the documented naming scheme) diff --git a/masonry/src/lib.rs b/masonry/src/lib.rs index c6830e904..3d87e191c 100644 --- a/masonry/src/lib.rs +++ b/masonry/src/lib.rs @@ -162,7 +162,6 @@ mod paint_scene_helpers; mod passes; mod render_root; mod tracing_backend; -mod tree_arena; pub mod event_loop_runner; pub mod testing; diff --git a/masonry/src/passes/accessibility.rs b/masonry/src/passes/accessibility.rs index 7d12e60ba..2527c022d 100644 --- a/masonry/src/passes/accessibility.rs +++ b/masonry/src/passes/accessibility.rs @@ -7,8 +7,8 @@ use vello::kurbo::Rect; use crate::passes::recurse_on_children; use crate::render_root::{RenderRoot, RenderRootState}; -use crate::tree_arena::ArenaMut; use crate::{AccessCtx, Widget, WidgetState}; +use tree_arena::ArenaMut; use super::enter_span_if; diff --git a/masonry/src/passes/anim.rs b/masonry/src/passes/anim.rs index cd34e8ee7..a7f8cc6b1 100644 --- a/masonry/src/passes/anim.rs +++ b/masonry/src/passes/anim.rs @@ -5,8 +5,8 @@ use tracing::info_span; use crate::passes::{enter_span_if, recurse_on_children}; use crate::render_root::{RenderRoot, RenderRootState}; -use crate::tree_arena::ArenaMut; use crate::{UpdateCtx, Widget, WidgetState}; +use tree_arena::ArenaMut; // --- MARK: UPDATE ANIM --- fn update_anim_for_widget( diff --git a/masonry/src/passes/compose.rs b/masonry/src/passes/compose.rs index 5ae018f80..20f3ee8fa 100644 --- a/masonry/src/passes/compose.rs +++ b/masonry/src/passes/compose.rs @@ -6,8 +6,8 @@ use vello::kurbo::Vec2; use crate::passes::{enter_span_if, recurse_on_children}; use crate::render_root::{RenderRoot, RenderRootSignal, RenderRootState}; -use crate::tree_arena::ArenaMut; use crate::{ComposeCtx, Widget, WidgetState}; +use tree_arena::ArenaMut; // --- MARK: RECURSE --- fn compose_widget( diff --git a/masonry/src/passes/mod.rs b/masonry/src/passes/mod.rs index d152b1529..4c57412c8 100644 --- a/masonry/src/passes/mod.rs +++ b/masonry/src/passes/mod.rs @@ -4,9 +4,9 @@ use tracing::span::EnteredSpan; use crate::render_root::RenderRootState; -use crate::tree_arena::{ArenaMut, ArenaMutChildren, ArenaRef}; use crate::widget::WidgetArena; use crate::{QueryCtx, Widget, WidgetId, WidgetState}; +use tree_arena::{ArenaMut, ArenaMutChildren, ArenaRef}; pub(crate) mod accessibility; pub(crate) mod anim; diff --git a/masonry/src/passes/paint.rs b/masonry/src/passes/paint.rs index 0842a35d9..041716306 100644 --- a/masonry/src/passes/paint.rs +++ b/masonry/src/passes/paint.rs @@ -11,8 +11,8 @@ use vello::Scene; use crate::passes::{enter_span_if, recurse_on_children}; use crate::render_root::{RenderRoot, RenderRootState}; use crate::theme::get_debug_color; -use crate::tree_arena::ArenaMut; use crate::{PaintCtx, Widget, WidgetId, WidgetState}; +use tree_arena::ArenaMut; // --- MARK: PAINT WIDGET --- fn paint_widget( diff --git a/masonry/src/passes/update.rs b/masonry/src/passes/update.rs index a733efecf..5f81e9011 100644 --- a/masonry/src/passes/update.rs +++ b/masonry/src/passes/update.rs @@ -9,10 +9,10 @@ use tracing::{info_span, trace}; use crate::passes::event::run_on_pointer_event_pass; use crate::passes::{enter_span, enter_span_if, merge_state_up, recurse_on_children}; use crate::render_root::{RenderRoot, RenderRootSignal, RenderRootState}; -use crate::tree_arena::ArenaMut; use crate::{ PointerEvent, QueryCtx, RegisterCtx, Update, UpdateCtx, Widget, WidgetId, WidgetState, }; +use tree_arena::ArenaMut; // --- MARK: HELPERS --- fn get_id_path(root: &RenderRoot, widget_id: Option) -> Vec { diff --git a/masonry/src/render_root.rs b/masonry/src/render_root.rs index c6c6a6114..5f72b24a0 100644 --- a/masonry/src/render_root.rs +++ b/masonry/src/render_root.rs @@ -35,9 +35,9 @@ use crate::passes::update::{ }; use crate::passes::{recurse_on_children, PassTracing}; use crate::text::BrushIndex; -use crate::tree_arena::{ArenaMut, TreeArena}; use crate::widget::{WidgetArena, WidgetMut, WidgetRef, WidgetState}; use crate::{AccessEvent, Action, CursorIcon, Handled, QueryCtx, Widget, WidgetId, WidgetPod}; +use tree_arena::{ArenaMut, TreeArena}; // --- MARK: STRUCTS --- diff --git a/masonry/src/widget/widget_arena.rs b/masonry/src/widget/widget_arena.rs index 54842e248..f914de31a 100644 --- a/masonry/src/widget/widget_arena.rs +++ b/masonry/src/widget/widget_arena.rs @@ -1,8 +1,8 @@ // Copyright 2024 the Xilem Authors // SPDX-License-Identifier: Apache-2.0 -use crate::tree_arena::{ArenaMut, ArenaRef, TreeArena}; use crate::{Widget, WidgetId, WidgetState}; +use tree_arena::{ArenaMut, ArenaRef, TreeArena}; pub(crate) struct WidgetArena { pub(crate) widgets: TreeArena>, diff --git a/tree_arena/ARCHITECTURE.md b/tree_arena/ARCHITECTURE.md new file mode 100644 index 000000000..e40e5658c --- /dev/null +++ b/tree_arena/ARCHITECTURE.md @@ -0,0 +1,33 @@ +# Tree Arena + +## Architecture + +The unsafe tree arena contains a `DataMap` which **owns** all nodes. The `DataMap` contains: + +* A `HashMap` associating `NodeId` with `Box>>`, owning the node data, (boxed to prevent movement of the node when the `HashMap` is resized and `UnsafeCell` to express the interior mutability) + +* A `HashMap` associating `NodeId` with `Option`, containing the parent information for the nodes + +* `Box>>` containing the roots of the tree + +It is possible to get shared (immutable) access or exclusive (mutable) access to the tree. These return `ArenaRef<'arena, T>` or `ArenaMut<'arena, T>` respectively + +### Shared References + +`ArenaRef<'arena, T>` contains the identity of the parent node, a reference to the node data, and `ArenaRefChildren<'arena, T>`. The `ArenaRefChildren<'arena, T>` contains the ids of the children of the node, the id of the node, and a reference to the arena. From this `ArenaRefChildren<'arena, T>` it is possible to get shared access to children of the node. + +### Exclusive References + +`ArenaMut<'arena, T>` contains the identity of the parent node, a mutable reference to the node data, and `ArenaMutChildren<'arena, T>`. The `ArenaMutChildren<'arena, T>` contains the ids of the children of the node, the id of the node, and a mutable reference to the arena. From this `ArenaMutChildren<'arena, T>` it is possible to get exclusive access to children of the node. + +## Safety + +From the `ArenaMutChildren<'arena, T>`, it is important that we can only access descendants of that node, such that we can only ever have exclusive mutable access to the contents of a node, and never have multiple mutable references. This invariant is not checked by the compiler and thus relies on the logic to determine whether a node is a descendant being correct. + +## Complexity + +Of finding children: $O(1)$ - previously $O(\text{children})$ + +Of finding deeper descendants: $O(\text{depth})$ - ideally will be made $O(1)$ + +Access from the root: $O(1)$, previously $O(\text{depth})$ - improved as all nodes are known to be descended from the root diff --git a/tree_arena/Cargo.toml b/tree_arena/Cargo.toml new file mode 100644 index 000000000..f022ee65a --- /dev/null +++ b/tree_arena/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "tree_arena" +version = "0.1.0" +description = "An arena allocated tree designed for Linebender" +keywords = ["arena", "tree"] +categories = ["gui"] +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true + +[dependencies] + +[features] +default = ["safe_tree"] +safe_tree = [] + +[lints] +workspace = true diff --git a/tree_arena/README.md b/tree_arena/README.md new file mode 100644 index 000000000..bd1b3321f --- /dev/null +++ b/tree_arena/README.md @@ -0,0 +1,3 @@ +# Tree Arena + +An arena allocated tree diff --git a/tree_arena/src/lib.rs b/tree_arena/src/lib.rs new file mode 100644 index 000000000..6af986a6f --- /dev/null +++ b/tree_arena/src/lib.rs @@ -0,0 +1,24 @@ +// Copyright 2024 the Xilem Authors +// SPDX-License-Identifier: Apache-2.0 + +//! This module will eventually be factored out into a separate crate. +//! +//! In the meantime, we intentionally don't make the types in this module part of +//! our public API, but still implement methods that a standalone crate would have. +//! +//! The types defined in this module don't *actually* implement an arena. They use +//! 100% safe code, which has a significant performance overhead. The final version +//! will use an arena and unsafe code, but should have the exact same exported API as +//! this module. + +type NodeId = u64; + +#[cfg(not(feature = "safe_tree"))] +mod tree_arena_unsafe; +#[cfg(not(feature = "safe_tree"))] +pub use tree_arena_unsafe::*; + +#[cfg(feature = "safe_tree")] +mod tree_arena_safe; +#[cfg(feature = "safe_tree")] +pub use tree_arena_safe::*; diff --git a/masonry/src/tree_arena.rs b/tree_arena/src/tree_arena_safe.rs similarity index 71% rename from masonry/src/tree_arena.rs rename to tree_arena/src/tree_arena_safe.rs index 00026c885..650454d48 100644 --- a/masonry/src/tree_arena.rs +++ b/tree_arena/src/tree_arena_safe.rs @@ -11,14 +11,15 @@ //! will use an arena and unsafe code, but should have the exact same exported API as //! this module. -#![allow(dead_code)] +use super::NodeId; use std::collections::HashMap; -struct TreeNode { - id: u64, - item: Item, - children: Vec>, +#[derive(Debug)] +struct TreeNode { + id: NodeId, + item: T, + children: Vec>, } // TODO - ArenaRefChildren and ArenaMutChildren might be easier to document if they were @@ -30,20 +31,24 @@ struct TreeNode { /// will keep track of parent-child relationships, lets you efficiently find /// an item anywhere in the tree hierarchy, and give you mutable access to this item /// and its children. -#[derive(Default)] -pub struct TreeArena { - roots: Vec>, - parents_map: HashMap>, +#[derive(Debug, Default)] +pub struct TreeArena { + roots: Vec>, + parents_map: HashMap>, } /// A reference type giving shared access to an arena item and its children. /// /// When you borrow an item from a [`TreeArena`], it returns an `ArenaRef`. /// You can iterate over its children to get access to child `ArenaRef` handles. -pub struct ArenaRef<'a, Item> { - pub parent_id: Option, - pub item: &'a Item, - pub children: ArenaRefChildren<'a, Item>, +#[derive(Debug)] +pub struct ArenaRef<'arena, T> { + /// The parent of this node + pub parent_id: Option, + /// The item in the node + pub item: &'arena T, + /// The children of the node + pub children: ArenaRefChildren<'arena, T>, } /// A reference type giving mutable access to an arena item and its children. @@ -58,68 +63,77 @@ pub struct ArenaRef<'a, Item> { /// and its children independently without invalidating the references. /// /// You can iterate over its children to get access to child `ArenaMut` handles. -pub struct ArenaMut<'a, Item> { - pub parent_id: Option, - pub item: &'a mut Item, - pub children: ArenaMutChildren<'a, Item>, +#[derive(Debug)] +pub struct ArenaMut<'arena, T> { + /// The parent of the node + pub parent_id: Option, + /// The itemn in the node + pub item: &'arena mut T, + /// The children of the node + pub children: ArenaMutChildren<'arena, T>, } /// A handle giving shared access to an arena item's children. /// /// See [`ArenaRef`] for more information. -pub struct ArenaRefChildren<'a, Item> { - id: Option, - children: &'a Vec>, - parents_map: ArenaMapRef<'a>, +#[derive(Debug)] +pub struct ArenaRefChildren<'arena, T> { + id: Option, + children: &'arena Vec>, + parents_map: ArenaMapRef<'arena>, } /// A handle giving mutable access to an arena item's children. /// /// See [`ArenaMut`] for more information. -pub struct ArenaMutChildren<'a, Item> { - id: Option, - children: &'a mut Vec>, - parents_map: ArenaMapMut<'a>, +#[derive(Debug)] +pub struct ArenaMutChildren<'arena, T> { + id: Option, + children: &'arena mut Vec>, + parents_map: ArenaMapMut<'arena>, } -#[derive(Clone, Copy)] -pub struct ArenaMapRef<'a> { - parents_map: &'a HashMap>, +/// A shared reference to the parent father map +#[derive(Clone, Copy, Debug)] +pub struct ArenaMapRef<'arena> { + parents_map: &'arena HashMap>, } -pub struct ArenaMapMut<'a> { - parents_map: &'a mut HashMap>, +/// A mutable reference to the parent father map +#[derive(Debug)] +pub struct ArenaMapMut<'arena> { + parents_map: &'arena mut HashMap>, } // -- MARK: IMPLS --- -impl<'a, Item> Clone for ArenaRef<'a, Item> { +impl Clone for ArenaRef<'_, T> { fn clone(&self) -> Self { *self } } -impl<'a, Item> Copy for ArenaRef<'a, Item> {} +impl Copy for ArenaRef<'_, T> {} -impl<'a, Item> Clone for ArenaRefChildren<'a, Item> { +impl Clone for ArenaRefChildren<'_, T> { fn clone(&self) -> Self { *self } } -impl<'a, Item> Copy for ArenaRefChildren<'a, Item> {} +impl Copy for ArenaRefChildren<'_, T> {} -impl TreeArena { +impl TreeArena { /// Create an empty tree. pub fn new() -> Self { - TreeArena { + Self { roots: Vec::new(), parents_map: HashMap::new(), } } /// Returns a handle whose children are the roots, if any, of the tree. - pub fn root_token(&self) -> ArenaRefChildren<'_, Item> { + pub fn root_token(&self) -> ArenaRefChildren<'_, T> { ArenaRefChildren { id: None, children: &self.roots, @@ -133,7 +147,7 @@ impl TreeArena { /// /// Using [`insert_child`](ArenaMutChildren::insert_child) on this handle /// will add a new root to the tree. - pub fn root_token_mut(&mut self) -> ArenaMutChildren<'_, Item> { + pub fn root_token_mut(&mut self) -> ArenaMutChildren<'_, T> { ArenaMutChildren { id: None, children: &mut self.roots, @@ -150,7 +164,7 @@ impl TreeArena { /// ## Complexity /// /// O(Depth). In future implementations, this will be O(1). - pub fn find(&self, id: impl Into) -> Option> { + pub fn find(&self, id: impl Into) -> Option> { self.root_token().find_inner(id.into()) } @@ -161,7 +175,7 @@ impl TreeArena { /// ## Complexity /// /// O(Depth). In future implementations, this will be O(1). - pub fn find_mut(&mut self, id: impl Into) -> Option> { + pub fn find_mut(&mut self, id: impl Into) -> Option> { self.root_token_mut().find_mut_inner(id.into()) } @@ -171,7 +185,7 @@ impl TreeArena { /// the root. /// /// If the id is not in the tree, returns an empty vector. - pub fn get_id_path(&self, id: impl Into) -> Vec { + pub fn get_id_path(&self, id: impl Into) -> Vec { let parents_map = ArenaMapRef { parents_map: &self.parents_map, }; @@ -179,12 +193,12 @@ impl TreeArena { } } -impl TreeNode { - fn arena_ref<'a>( - &'a self, - parent_id: Option, - parents_map: &'a HashMap>, - ) -> ArenaRef<'a, Item> { +impl TreeNode { + fn arena_ref<'arena>( + &'arena self, + parent_id: Option, + parents_map: &'arena HashMap>, + ) -> ArenaRef<'arena, T> { ArenaRef { parent_id, item: &self.item, @@ -196,11 +210,11 @@ impl TreeNode { } } - fn arena_mut<'a>( - &'a mut self, - parent_id: Option, - parents_map: &'a mut HashMap>, - ) -> ArenaMut<'a, Item> { + fn arena_mut<'arena>( + &'arena mut self, + parent_id: Option, + parents_map: &'arena mut HashMap>, + ) -> ArenaMut<'arena, T> { ArenaMut { parent_id, item: &mut self.item, @@ -213,23 +227,29 @@ impl TreeNode { } } -impl<'a, Item> ArenaRef<'a, Item> { +impl ArenaRef<'_, T> { /// Id of the item this handle is associated with. - pub fn id(&self) -> u64 { - // ArenaRefChildren always has an id when it's a member of ArenaRef + #[expect( + clippy::missing_panics_doc, + reason = "ArenaRefChildren always has an id when it's a member of ArenaRef" + )] + pub fn id(&self) -> NodeId { self.children.id.unwrap() } } -impl<'a, Item> ArenaMut<'a, Item> { +impl ArenaMut<'_, T> { /// Id of the item this handle is associated with. - pub fn id(&self) -> u64 { - // ArenaMutChildren always has an id when it's a member of ArenaMut + #[expect( + clippy::missing_panics_doc, + reason = "ArenaRefChildren always has an id when it's a member of ArenaRef" + )] + pub fn id(&self) -> NodeId { self.children.id.unwrap() } /// Returns a shared reference equivalent to this one. - pub fn reborrow(&mut self) -> ArenaRef<'_, Item> { + pub fn reborrow(&mut self) -> ArenaRef<'_, T> { ArenaRef { parent_id: self.parent_id, item: self.item, @@ -240,7 +260,7 @@ impl<'a, Item> ArenaMut<'a, Item> { /// Returns a mutable reference equivalent to this one. /// /// This is sometimes useful to work with the borrow checker. - pub fn reborrow_mut(&mut self) -> ArenaMut<'_, Item> { + pub fn reborrow_mut(&mut self) -> ArenaMut<'_, T> { ArenaMut { parent_id: self.parent_id, item: self.item, @@ -249,9 +269,9 @@ impl<'a, Item> ArenaMut<'a, Item> { } } -impl<'a, Item> ArenaRefChildren<'a, Item> { +impl<'arena, T> ArenaRefChildren<'arena, T> { /// Returns true if the handle has a child with the given id. - pub fn has_child(self, id: impl Into) -> bool { + pub fn has_child(self, id: impl Into) -> bool { let id = id.into(); self.children.iter().any(|child| child.id == id) } @@ -260,7 +280,7 @@ impl<'a, Item> ArenaRefChildren<'a, Item> { /// /// Returns a tuple of a shared reference to the child and a handle to access /// its children. - pub fn get_child(&self, id: impl Into) -> Option> { + pub fn get_child(&self, id: impl Into) -> Option> { let id = id.into(); self.children .iter() @@ -272,7 +292,7 @@ impl<'a, Item> ArenaRefChildren<'a, Item> { /// /// This is the same as [`get_child`](Self::get_child), except it consumes the /// handle. This is sometimes necessary to accommodate the borrow checker. - pub fn into_child(self, id: impl Into) -> Option> { + pub fn into_child(self, id: impl Into) -> Option> { let id = id.into(); self.children .iter() @@ -287,11 +307,11 @@ impl<'a, Item> ArenaRefChildren<'a, Item> { /// ## Complexity /// /// O(Depth). In future implementations, this will be O(1). - pub fn find(self, id: impl Into) -> Option> { + pub fn find(self, id: impl Into) -> Option> { self.find_inner(id.into()) } - fn find_inner(self, id: u64) -> Option> { + fn find_inner(self, id: NodeId) -> Option> { let parent_id = self.parents_map.parents_map.get(&id)?; let id_path = if let Some(parent_id) = parent_id { @@ -302,26 +322,25 @@ impl<'a, Item> ArenaRefChildren<'a, Item> { let mut id_path = id_path.as_slice(); let mut node_children = self.children; - while let Some((id, new_id_path)) = id_path.split_last() { + while let Some((ancestor_id, new_id_path)) = id_path.split_last() { id_path = new_id_path; node_children = &node_children .iter() - .find(|child| child.id == *id) - .unwrap() + .find(|child| child.id == *ancestor_id)? .children; } - let node = node_children.iter().find(|child| child.id == id).unwrap(); + let node = node_children.iter().find(|child| child.id == id)?; Some(node.arena_ref(*parent_id, self.parents_map.parents_map)) } } -impl<'a, Item> ArenaMutChildren<'a, Item> { +impl<'arena, T> ArenaMutChildren<'arena, T> { /// Get the child of the item this handle is associated with, which has the given id. /// /// Returns a tuple of a shared reference to the child and a handle to access /// its children. - pub fn get_child(&self, id: impl Into) -> Option> { + pub fn get_child(&self, id: impl Into) -> Option> { let id = id.into(); self.children .iter() @@ -333,7 +352,7 @@ impl<'a, Item> ArenaMutChildren<'a, Item> { /// /// Returns a tuple of a mutable reference to the child and a handle to access /// its children. - pub fn get_child_mut(&mut self, id: impl Into) -> Option> { + pub fn get_child_mut(&mut self, id: impl Into) -> Option> { let id = id.into(); self.children .iter_mut() @@ -345,7 +364,7 @@ impl<'a, Item> ArenaMutChildren<'a, Item> { /// /// This is the same as [`get_child`](Self::get_child), except it consumes the /// handle. This is sometimes necessary to accommodate the borrow checker. - pub fn into_child(self, id: impl Into) -> Option> { + pub fn into_child(self, id: impl Into) -> Option> { let id = id.into(); self.children .iter() @@ -357,7 +376,7 @@ impl<'a, Item> ArenaMutChildren<'a, Item> { /// /// This is the same as [`get_child_mut`](Self::get_child_mut), except it consumes /// the handle. This is sometimes necessary to accommodate the borrow checker. - pub fn into_child_mut(self, id: impl Into) -> Option> { + pub fn into_child_mut(self, id: impl Into) -> Option> { let id = id.into(); self.children .iter_mut() @@ -376,9 +395,12 @@ impl<'a, Item> ArenaMutChildren<'a, Item> { /// /// The `insert_child` method will panic if the arena already contains a child /// with the given id. - pub fn insert_child(&mut self, child_id: impl Into, value: Item) { + pub fn insert_child(&mut self, child_id: impl Into, value: T) { let child_id = child_id.into(); - assert!(!self.parents_map.parents_map.contains_key(&child_id)); + assert!( + !self.parents_map.parents_map.contains_key(&child_id), + "Key already present" + ); self.parents_map.parents_map.insert(child_id, self.id); self.children.push(TreeNode { @@ -396,18 +418,21 @@ impl<'a, Item> ArenaMutChildren<'a, Item> { /// /// Calling this will silently remove any recursive grandchildren of this item. #[must_use] - pub fn remove_child(&mut self, child_id: impl Into) -> Option { + pub fn remove_child(&mut self, child_id: impl Into) -> Option { let child_id = child_id.into(); let i = self .children .iter() .position(|child| child.id == child_id)?; - fn remove_children(node: &TreeNode, parents_map: &mut HashMap>) { - parents_map.remove(&node.id); + fn remove_children( + node: &TreeNode, + parents_map: &mut HashMap>, + ) { for child in &node.children { remove_children(child, parents_map); } + parents_map.remove(&node.id); } let child = self.children.remove(i); @@ -417,7 +442,7 @@ impl<'a, Item> ArenaMutChildren<'a, Item> { } /// Returns a shared handle equivalent to this one. - pub fn reborrow(&self) -> ArenaRefChildren<'_, Item> { + pub fn reborrow(&self) -> ArenaRefChildren<'_, T> { ArenaRefChildren { id: self.id, children: &*self.children, @@ -428,7 +453,7 @@ impl<'a, Item> ArenaMutChildren<'a, Item> { /// Returns a mutable handle equivalent to this one. /// /// This is sometimes useful to work with the borrow checker. - pub fn reborrow_mut(&mut self) -> ArenaMutChildren<'_, Item> { + pub fn reborrow_mut(&mut self) -> ArenaMutChildren<'_, T> { ArenaMutChildren { id: self.id, children: &mut *self.children, @@ -443,7 +468,7 @@ impl<'a, Item> ArenaMutChildren<'a, Item> { /// ## Complexity /// /// O(Depth). In future implementations, this will be O(1). - pub fn find(&self, id: impl Into) -> Option> { + pub fn find(&self, id: impl Into) -> Option> { self.reborrow().find(id) } @@ -454,11 +479,11 @@ impl<'a, Item> ArenaMutChildren<'a, Item> { /// ## Complexity /// /// O(Depth). In future implementations, this will be O(1). - pub fn find_mut(self, id: impl Into) -> Option> { + pub fn find_mut(self, id: impl Into) -> Option> { self.find_mut_inner(id.into()) } - fn find_mut_inner(self, id: u64) -> Option> { + fn find_mut_inner(self, id: NodeId) -> Option> { let parent_id = self.parents_map.parents_map.get(&id)?; let id_path = if let Some(parent_id) = parent_id { @@ -468,25 +493,21 @@ impl<'a, Item> ArenaMutChildren<'a, Item> { }; let mut id_path = id_path.as_slice(); - let mut node_children: &'a mut _ = &mut *self.children; - while let Some((id, new_id_path)) = id_path.split_last() { + let mut node_children: &'arena mut _ = &mut *self.children; + while let Some((ancestor_id, new_id_path)) = id_path.split_last() { id_path = new_id_path; node_children = &mut node_children .iter_mut() - .find(|child| child.id == *id) - .unwrap() + .find(|child| child.id == *ancestor_id)? .children; } - let node = node_children - .iter_mut() - .find(|child| child.id == id) - .unwrap(); + let node = node_children.iter_mut().find(|child| child.id == id)?; Some(node.arena_mut(*parent_id, &mut *self.parents_map.parents_map)) } } -impl<'a> ArenaMapRef<'a> { +impl ArenaMapRef<'_> { /// Construct the path of items from the given item to the root of the tree. /// /// The path is in order from the bottom to the top, starting at the given item and ending at @@ -495,7 +516,11 @@ impl<'a> ArenaMapRef<'a> { /// If `start_id` is Some, the path ends just before that id instead; `start_id` is not included. /// /// If there is no path from `start_id` to id, returns an empty vector. - pub fn get_id_path(self, id: u64, start_id: Option) -> Vec { + #[expect( + clippy::missing_panics_doc, + reason = "All ids in the tree should have a parent in the parent map" + )] + pub fn get_id_path(self, id: NodeId, start_id: Option) -> Vec { let mut path = Vec::new(); if !self.parents_map.contains_key(&id) { @@ -503,23 +528,22 @@ impl<'a> ArenaMapRef<'a> { } let mut current_id = Some(id); - while let Some(id) = current_id { - path.push(id); - current_id = *self.parents_map.get(&id).unwrap(); + while let Some(current) = current_id { + path.push(current); + current_id = *self.parents_map.get(¤t).unwrap(); + if current_id == start_id { + break; + } } - if let Some(start_id) = start_id { - while let Some(id) = path.pop() { - if id == start_id { - break; - } - } + if current_id != start_id { + path.clear(); } path } } -impl<'a> ArenaMapMut<'a> { +impl ArenaMapMut<'_> { /// Returns a shared handle equivalent to this one. pub fn reborrow(&self) -> ArenaMapRef<'_> { ArenaMapRef { @@ -544,27 +568,7 @@ impl<'a> ArenaMapMut<'a> { /// If `start_id` is Some, the path ends just before that id instead; `start_id` is not included. /// /// If there is no path from `start_id` to id, returns an empty vector. - pub fn get_id_path(&self, id: u64, start_id: Option) -> Vec { + pub fn get_id_path(&self, id: NodeId, start_id: Option) -> Vec { self.reborrow().get_id_path(id, start_id) } } - -// This is a sketch of what the unsafe version of this code would look like, -// one with an actual arena. -#[cfg(FALSE)] -mod arena_version { - struct TreeArena { - items: HashMap>, - parents: HashMap>, - } - - struct ArenaRefChildren<'a, Item> { - arena: &'a TreeArena, - id: u64, - } - - struct ArenaMutChildren<'a, Item> { - arena: &'a TreeArena, - id: u64, - } -} diff --git a/tree_arena/src/tree_arena_unsafe.rs b/tree_arena/src/tree_arena_unsafe.rs new file mode 100644 index 000000000..47cefa9c8 --- /dev/null +++ b/tree_arena/src/tree_arena_unsafe.rs @@ -0,0 +1,612 @@ +// Copyright 2024 the Xilem Authors +// SPDX-License-Identifier: Apache-2.0 + +#![allow(unsafe_code, reason = "Purpose is unsafe abstraction")] +use super::NodeId; + +use std::cell::UnsafeCell; +use std::collections::HashMap; + +#[derive(Debug)] +struct TreeNode { + item: T, + children: Vec, +} + +/// Mapping of data for the Tree Arena +#[derive(Debug)] +struct DataMap { + /// The items in the tree + items: HashMap>>>, + /// The parent of each node, or None if it is the root + parents: HashMap>, + /// The roots of the tree + roots: Box>>, +} + +/// A container type for a tree of items. +/// +/// This type is used to store zero, one or many trees of a given item type. It +/// will keep track of parent-child relationships, lets you efficiently find +/// an item anywhere in the tree hierarchy, and give you mutable access to this item +/// and its children. +#[derive(Debug)] +pub struct TreeArena { + /// The items in the tree + data_map: DataMap, +} + +/// A reference type giving shared access to an arena item and its children. +/// +/// When you borrow an item from a [`TreeArena`], it returns an [`ArenaRef`]. +/// You can access it children to get access to child [`ArenaRef`] handles. +#[derive(Debug)] +pub struct ArenaRef<'arena, T> { + /// Parent of the Node + pub parent_id: Option, + /// Item in the node + pub item: &'arena T, + /// Children of the node + pub children: ArenaRefChildren<'arena, T>, +} + +/// A handle giving shared access to an arena item's children. +/// +/// See [`ArenaRef`] for more information. +#[derive(Debug)] +pub struct ArenaRefChildren<'arena, T> { + /// The associated data arena + parent_arena: &'arena DataMap, + /// The parent id for these children + id: Option, + /// Array of children + #[expect(dead_code, reason = "Kept for possible future use")] + child_arr: &'arena [NodeId], +} + +impl Clone for ArenaRef<'_, Item> { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for ArenaRef<'_, Item> {} + +impl Clone for ArenaRefChildren<'_, T> { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for ArenaRefChildren<'_, Item> {} + +/// A reference type giving mutable access to an arena item and its children. +/// +/// When you borrow an item from a [`TreeArena`], it returns an `ArenaMut`. +/// This struct holds three fields: +/// - the id of its parent. +/// - a reference to the item itself. +/// - an [`ArenaMutChildren`] handle to access its children. +/// +/// Because the latter two are disjoint references, you can mutate the node's value +/// and its children independently without invalidating the references. +/// +/// You can iterate over its children to get access to child `ArenaMut` handles. +#[derive(Debug)] +pub struct ArenaMut<'arena, T> { + /// Parent of the Node + pub parent_id: Option, + /// Item in the node + pub item: &'arena mut T, + /// Children of the node + pub children: ArenaMutChildren<'arena, T>, +} + +/// A handle giving mutable access to an arena item's children. +/// +/// See [`ArenaMut`] for more information. +/// +/// This stores all the permissions for what nodes can be accessed from the current node +/// As such if a [`std::mem::swap`] is used to swap the children of two trees, +/// each tree will still have the correct permissions. This also stores the roots, and so +/// that will also be in a consistent state +#[derive(Debug)] +pub struct ArenaMutChildren<'arena, T> { + /// The associated data arena + parent_arena: &'arena mut DataMap, + /// The parent id for these children + id: Option, + /// Array of children + child_arr: &'arena mut Vec, +} + +impl DataMap { + fn new() -> Self { + Self { + items: HashMap::new(), + parents: HashMap::new(), + roots: Box::new(UnsafeCell::new(Vec::new())), + } + } + + /// Find an item in the tree. + /// + /// Returns a shared reference to the item if present. + /// + /// Time Complexity O(1) + fn find(&self, id: impl Into) -> Option> { + // SAFETY + // We need there to be no mutable access to the node + // Mutable access to the node would imply there is some &mut self + // As we are taking &self, there can be no mutable access to the node + // Thus this is safe + let id: NodeId = id.into(); + let parent_id = *self.parents.get(&id)?; + self.items + .get(&id) + .and_then(|item| unsafe { item.get().as_ref() }) + .map( + |TreeNode { + item, + children: child_arr, + }| { + let children = ArenaRefChildren { + parent_arena: self, + id: Some(id), + child_arr, + }; + ArenaRef { + parent_id, + item, + children, + } + }, + ) + } + + /// Find an item in the tree. + /// + /// Returns a mutable reference to the item if present. + /// + /// Time Complexity O(1) + /// + /// # SAFETY + /// + /// When using this on [`ArenaMutChildren`] associated with some node, + /// must ensure that `id` is a descendant of that node, otherwise can + /// obtain two mutable references to the same node + unsafe fn find_mut(&mut self, id: impl Into) -> Option> { + let id: NodeId = id.into(); + let parent_id = *self.parents.get(&id)?; + self.items + .get(&id) + .and_then(|item| unsafe { item.get().as_mut() }) + .map( + |TreeNode { + item, + children: child_arr, + }| { + let children = ArenaMutChildren { + parent_arena: self, + id: Some(id), + child_arr, + }; + ArenaMut { + parent_id, + item, + children, + } + }, + ) + } + + /// Construct the path of items from the given item to the root of the tree. + /// + /// The path is in order from the bottom to the top, starting at the given item and ending at + /// the root. + /// + /// If `start_id` is Some, the path ends just before that id instead; `start_id` is not included. + /// + /// If there is no path from `start_id` to id, returns the empty vector. + fn get_id_path(&self, id: impl Into, start_id: Option) -> Vec { + let id: NodeId = id.into(); + let mut path = Vec::new(); + + if !self.parents.contains_key(&id) { + return path; + } + + let mut current_id = Some(id); + while let Some(current) = current_id { + path.push(current); + current_id = *self.parents.get(¤t).unwrap(); + if current_id == start_id { + break; + } + } + + // current_id was the last parent node + // as such if current id is not start_id + // we have gone to the root and we empty the vec + if current_id != start_id { + path.clear(); + } + path + } +} + +impl TreeArena { + /// Create a new empty tree + pub fn new() -> Self { + Self { + data_map: DataMap::new(), + } + } + + /// Returns a handle whose children are the roots, if any, of the tree. + #[expect( + clippy::missing_panics_doc, + reason = "roots should not point at a null value" + )] + pub fn root_token(&self) -> ArenaRefChildren<'_, T> { + // safe as the roots are derived from the arena itself (same as safety for find for non root nodes) + let roots = unsafe { self.data_map.roots.get().as_ref().unwrap() }; + ArenaRefChildren { + parent_arena: &self.data_map, + id: None, + child_arr: roots, + } + } + + /// Returns a handle whose children are the roots, if any, of the tree. + /// + /// Using [`insert_child`](ArenaMutChildren::insert_child) on this handle + /// will add a new root to the tree. + #[expect( + clippy::missing_panics_doc, + reason = "roots should not point at a null value" + )] + pub fn root_token_mut(&mut self) -> ArenaMutChildren<'_, T> { + // safe as the roots are derived from the arena itself (same as safety for find for non root nodes) + let roots = unsafe { self.data_map.roots.get().as_mut().unwrap() }; + ArenaMutChildren { + parent_arena: &mut self.data_map, + id: None, + child_arr: roots, + } + } + + /// Find an item in the tree. + /// + /// Returns a shared reference to the item if present. + /// + /// ## Complexity + /// + /// O(1). + pub fn find(&self, id: impl Into) -> Option> { + self.data_map.find(id) + } + + /// Find an item in the tree. + /// + /// Returns a mutable reference to the item if present. + pub fn find_mut(&mut self, id: impl Into) -> Option> { + // safe as derived from the arena itself and has assoc lifetime with the arena + unsafe { self.data_map.find_mut(id) } + } + + /// Construct the path of items from the given item to the root of the tree. + /// + /// The path is in order from the bottom to the top, starting at the given item and ending at + /// the root. + /// + /// If the id is not in the tree, returns an empty vector. + pub fn get_id_path(&self, id: impl Into) -> Vec { + self.data_map.get_id_path(id, None) + } +} + +impl Default for TreeArena { + fn default() -> Self { + Self::new() + } +} + +impl ArenaRef<'_, T> { + /// Id of the item this handle is associated with. + #[expect( + clippy::missing_panics_doc, + reason = "ArenaRefChildren always has an id when it's a member of ArenaRef" + )] + pub fn id(&self) -> NodeId { + self.children.id.unwrap() + } +} + +impl<'arena, T> ArenaRefChildren<'arena, T> { + /// Check if id is a descendant of self + /// O(depth) and the limiting factor for find methods + /// not from the root + fn is_descendant(&self, id: NodeId) -> bool { + // the id of the parent + let parent_id = self.id; + + // The arena is derived from the root, and the id is in the tree + if parent_id.is_none() && self.parent_arena.items.contains_key(&id) { + return true; + } + + // if the path is empty, there is no path from id to self + !self.parent_arena.get_id_path(id, parent_id).is_empty() + } + + /// returns true if there is a child with the given id + pub fn has_child(&self, id: impl Into) -> bool { + let child_id = id.into(); + let parent_id = self.id; + self.parent_arena + .parents + .get(&child_id) + .map(|parent| *parent == parent_id) // check if the parent of child is the same as the parent of the arena + .unwrap_or_default() + } + + /// Get the child of the item this handle is associated with, which has the given id. + /// + /// Return a new [`ArenaRef`] + pub fn get_child(&self, id: impl Into) -> Option> { + let id = id.into(); + if self.has_child(id) { + self.parent_arena.find(id) + } else { + None + } + } + + /// Get the child of the item this handle is associated with, which has the given id. + /// + /// This is the same as [`get_child`](Self::get_child), except it consumes the + /// handle. This is sometimes necessary to accommodate the borrow checker. + pub fn into_child(self, id: impl Into) -> Option> { + let id = id.into(); + if self.has_child(id) { + self.parent_arena.find(id) + } else { + None + } + } + + /// Find an arena item among descendants (this node not included). + /// + /// Returns a shared reference to the item if present. + /// + /// ## Complexity + /// + /// O(Depth). In future implementations, this will be O(1). + pub fn find(self, id: impl Into) -> Option> { + // the id to search for + let id: NodeId = id.into(); + + if self.is_descendant(id) { + self.parent_arena.find(id) + } else { + None + } + } +} + +impl ArenaMut<'_, T> { + /// Id of the item this handle is associated with + #[expect( + clippy::missing_panics_doc, + reason = "ArenaMutChildren always has an id when it's a member of ArenaMut" + )] + pub fn id(&self) -> NodeId { + self.children.id.unwrap() + } + + /// Returns a shared reference equivalent to this one. + pub fn reborrow(&mut self) -> ArenaRef<'_, T> { + ArenaRef { + parent_id: self.parent_id, + item: self.item, + children: self.children.reborrow(), + } + } + + /// Returns a mutable reference equivalent to this one. + /// + /// This is sometimes useful to work with the borrow checker. + pub fn reborrow_mut(&mut self) -> ArenaMut<'_, T> { + ArenaMut { + parent_id: self.parent_id, + item: self.item, + children: self.children.reborrow_mut(), + } + } +} + +impl<'arena, T> ArenaMutChildren<'arena, T> { + /// Check if id is a descendant of self + /// O(depth) and the limiting factor for find methods + /// not from the root + fn is_descendant(&self, id: NodeId) -> bool { + self.reborrow().is_descendant(id) + } + + /// returns true if there is a child with the given id + pub fn has_child(&self, id: impl Into) -> bool { + let child_id = id.into(); + let parent_id = self.id; + self.parent_arena + .parents + .get(&child_id) + .map(|parent| *parent == parent_id) // check if the parent of child is the same as the parent of the arena + .unwrap_or_default() + } + + /// Get the child of the item this handle is associated with, which has the given id. + /// + /// Returns a tuple of a mutable reference to the child and a handle to access + /// its children. + pub fn get_child(&self, id: impl Into) -> Option> { + let id = id.into(); + if self.has_child(id) { + self.parent_arena.find(id) + } else { + None + } + } + + /// Get the child of the item this handle is associated with, which has the given id. + /// + /// Returns a tuple of a mutable reference to the child and a handle to access + /// its children. + pub fn get_child_mut(&mut self, id: impl Into) -> Option> { + let id = id.into(); + if self.has_child(id) { + // safe as we check the node is a direct child node + unsafe { self.parent_arena.find_mut(id) } + } else { + None + } + } + + /// Get the child of the item this handle is associated with, which has the given id. + /// + /// This is the same as [`get_child`](Self::get_child), except it consumes the + /// handle. This is sometimes necessary to accommodate the borrow checker. + pub fn into_child(self, id: impl Into) -> Option> { + let id = id.into(); + if self.has_child(id) { + self.parent_arena.find(id) + } else { + None + } + } + + /// Get the child of the item this handle is associated with, which has the given id. + /// + /// This is the same as [`get_child_mut`](Self::get_child_mut), except it consumes + /// the handle. This is sometimes necessary to accommodate the borrow checker. + pub fn into_child_mut(self, id: impl Into) -> Option> { + let id = id.into(); + if self.has_child(id) { + // safe as we check the node is a direct child node + unsafe { self.parent_arena.find_mut(id) } + } else { + None + } + } + + // TODO - Remove the child_id argument once creation of Widgets is figured out. + // Return the id instead. + /// Insert a child into the tree under the item associated with this handle. + /// + /// The new child will have the given id. + /// + /// # Panics + /// + /// The `insert_child` method will panic if the arena already contains a child + /// with the given id. + pub fn insert_child(&mut self, child_id: impl Into, value: T) { + let child_id: NodeId = child_id.into(); + assert!( + !self.parent_arena.parents.contains_key(&child_id), + "Key already present" + ); + + self.parent_arena.parents.insert(child_id, self.id); + + self.child_arr.push(child_id); + + let node = TreeNode { + item: value, + children: Vec::new(), + }; + + self.parent_arena + .items + .insert(child_id, Box::new(UnsafeCell::new(node))); + } + + // TODO - How to handle when a subtree is removed? + // Move children to the root? + // Should this be must use? + /// Remove the child with the given id from the tree. + /// + /// Returns the removed item, or None if no child with the given id exists. + /// + /// Calling this will silently remove any recursive grandchildren of this item. + #[must_use] + pub fn remove_child(&mut self, child_id: impl Into) -> Option { + let child_id: NodeId = child_id.into(); + if self.has_child(child_id) { + fn remove_children(id: NodeId, data_map: &mut DataMap) -> T { + let node = data_map.items.remove(&id).unwrap().into_inner(); + for child_id in node.children.into_iter() { + remove_children(child_id, data_map); + } + data_map.parents.remove(&id); + node.item + } + self.child_arr.retain(|i| *i != child_id); + Some(remove_children(child_id, self.parent_arena)) + } else { + None + } + } + + /// Returns a shared handle equivalent to this one. + pub fn reborrow(&self) -> ArenaRefChildren<'_, T> { + ArenaRefChildren { + parent_arena: self.parent_arena, + id: self.id, + child_arr: self.child_arr, + } + } + + /// Returns a mutable handle equivalent to this one. + /// + /// This is sometimes useful to work with the borrow checker. + pub fn reborrow_mut(&mut self) -> ArenaMutChildren<'_, T> { + ArenaMutChildren { + parent_arena: self.parent_arena, + id: self.id, + child_arr: self.child_arr, + } + } + + /// Find an arena item among descendants (this node not included). + /// + /// Returns a shared reference to the item if present. + /// + /// ## Complexity + /// + /// O(Depth). In future implementations, this will be O(1). + pub fn find(self, id: impl Into) -> Option> { + let id = id.into(); + if self.is_descendant(id) { + self.parent_arena.find(id) + } else { + None + } + } + + /// Find an arena item among descendants (this node not included). + /// + /// Returns a shared reference to the item if present. + /// + /// ## Complexity + /// + /// O(Depth). In future implementations, this will be O(1). + pub fn find_mut(self, id: impl Into) -> Option> { + let id = id.into(); + if self.is_descendant(id) { + // safe as we check the node is a descendant + unsafe { self.parent_arena.find_mut(id) } + } else { + None + } + } +} diff --git a/tree_arena/tests/basic_tests.rs b/tree_arena/tests/basic_tests.rs new file mode 100644 index 000000000..51dccc57c --- /dev/null +++ b/tree_arena/tests/basic_tests.rs @@ -0,0 +1,104 @@ +// Copyright 2024 the Xilem Authors +// SPDX-License-Identifier: Apache-2.0 + +use tree_arena::*; + +#[test] +fn arena_tree_test() { + let mut tree: TreeArena = TreeArena::new(); + let mut roots = tree.root_token_mut(); + roots.insert_child(1_u64, 'a'); + roots.insert_child(2_u64, 'b'); + let mut child_1 = roots.get_child_mut(1_u64).expect("No child 1 found"); + child_1.children.insert_child(3_u64, 'c'); + + let mut child_3 = child_1 + .children + .get_child_mut(3_u64) + .expect("No child 3 found"); + child_3.children.insert_child(4_u64, 'd'); + + let child_2 = tree.find(2_u64).expect("No child 2 found"); + let child_4 = child_2.children.find(4_u64); + assert!( + child_4.is_none(), + "Child 4 should not be descended from Child 2" + ); +} + +#[test] +fn arena_tree_removal_test() { + let mut tree: TreeArena = TreeArena::new(); + let mut roots = tree.root_token_mut(); + roots.insert_child(1_u64, 'a'); + roots.insert_child(2_u64, 'b'); + let mut child_1 = roots.get_child_mut(1_u64).expect("No child 1 found"); + child_1.children.insert_child(3_u64, 'c'); + + let mut child_3 = child_1 + .children + .get_child_mut(3_u64) + .expect("No child 3 found"); + child_3.children.insert_child(4_u64, 'd'); + + let child_3_removed = child_1 + .children + .remove_child(3_u64) + .expect("No child 3 found"); + assert_eq!(child_3_removed, 'c', "Expect removal of node 3"); + + let no_child_3_removed = child_1.children.remove_child(3_u64); + assert!(no_child_3_removed.is_none(), "Child 3 was not removed"); +} + +#[test] +#[should_panic(expected = "Key already present")] +fn arena_tree_duplicate_insertion() { + let mut tree: TreeArena = TreeArena::new(); + let mut roots = tree.root_token_mut(); + roots.insert_child(1_u64, 'a'); + roots.insert_child(1_u64, 'b'); +} + +#[test] +fn parent_child_items() { + let mut tree: TreeArena = TreeArena::new(); + let mut roots = tree.root_token_mut(); + roots.insert_child(1_u64, 'a'); + let mut node_1 = roots.get_child_mut(1_u64).expect("No child 1 found"); + node_1.children.insert_child(2_u64, 'b'); + let node_1_item = node_1.item; + let node_2_item = node_1 + .children + .get_child_mut(2_u64) + .expect("No child 2 found") + .item; + *node_1_item = 'c'; + *node_2_item = 'd'; + assert_eq!(*node_1_item, 'c', "Node 1 item should be 'c'"); + assert_eq!(*node_2_item, 'd', "Node 2 item should be 'd'"); +} + +#[test] +fn mem_swap() { + let mut tree: TreeArena = TreeArena::new(); + let mut roots = tree.root_token_mut(); + roots.insert_child(1_u64, 'a'); + let mut node_1 = roots.get_child_mut(1_u64).expect("No child 1 found"); + node_1.children.insert_child(2_u64, 'b'); + let node_1_item = node_1.item; + let node_2 = node_1 + .children + .get_child_mut(2_u64) + .expect("No child 2 found"); + let node_2_item = node_2.item; + *node_1_item = 'c'; + *node_2_item = 'd'; + #[expect( + clippy::drop_non_drop, + reason = "Drop glue may be added for future trees, and may differ between safe and unsafe versions" + )] + drop(node_2.children); + assert_eq!(*node_1_item, 'c', "Node 1 item should be 'c'"); + assert_eq!(*node_2_item, 'd', "Node 2 item should be 'd'"); +}