diff --git a/Cargo.lock b/Cargo.lock index ff41998c7..df4f1f562 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1974,6 +1974,7 @@ dependencies = [ "tracing-subscriber", "tracing-tracy", "tracing_android_trace", + "tree_arena", "vello", "web-time", "wgpu", @@ -3718,6 +3719,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 232734006..93e3c5494 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 a5d38906e..823ba89eb 100644 --- a/masonry/src/contexts.rs +++ b/masonry/src/contexts.rs @@ -9,6 +9,7 @@ use accesskit::TreeUpdate; use dpi::LogicalPosition; use parley::{FontContext, LayoutContext}; use tracing::{trace, warn}; +use tree_arena::{ArenaMutChildren, ArenaRefChildren}; use vello::kurbo::Vec2; use vello::peniko::Color; use winit::window::ResizeDirection; @@ -18,7 +19,6 @@ 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}; 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 332813004..03b61ff25 100644 --- a/masonry/src/passes/accessibility.rs +++ b/masonry/src/passes/accessibility.rs @@ -3,11 +3,11 @@ use accesskit::{Node, NodeId, Tree, TreeUpdate}; use tracing::{debug, info_span, trace}; +use tree_arena::ArenaMut; 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 super::enter_span_if; diff --git a/masonry/src/passes/anim.rs b/masonry/src/passes/anim.rs index cd34e8ee7..0b895aa18 100644 --- a/masonry/src/passes/anim.rs +++ b/masonry/src/passes/anim.rs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use tracing::info_span; +use tree_arena::ArenaMut; 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}; // --- MARK: UPDATE ANIM --- diff --git a/masonry/src/passes/compose.rs b/masonry/src/passes/compose.rs index 5ae018f80..88cf7edc0 100644 --- a/masonry/src/passes/compose.rs +++ b/masonry/src/passes/compose.rs @@ -2,11 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 use tracing::info_span; +use tree_arena::ArenaMut; 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}; // --- MARK: RECURSE --- diff --git a/masonry/src/passes/mod.rs b/masonry/src/passes/mod.rs index d152b1529..8779b71b2 100644 --- a/masonry/src/passes/mod.rs +++ b/masonry/src/passes/mod.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use tracing::span::EnteredSpan; +use tree_arena::{ArenaMut, ArenaMutChildren, ArenaRef}; use crate::render_root::RenderRootState; -use crate::tree_arena::{ArenaMut, ArenaMutChildren, ArenaRef}; use crate::widget::WidgetArena; use crate::{QueryCtx, Widget, WidgetId, WidgetState}; diff --git a/masonry/src/passes/paint.rs b/masonry/src/passes/paint.rs index 0842a35d9..fb059b44c 100644 --- a/masonry/src/passes/paint.rs +++ b/masonry/src/passes/paint.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; use tracing::{info_span, trace}; +use tree_arena::ArenaMut; use vello::kurbo::{Affine, Stroke}; use vello::peniko::Mix; use vello::Scene; @@ -11,7 +12,6 @@ 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}; // --- MARK: PAINT WIDGET --- diff --git a/masonry/src/passes/update.rs b/masonry/src/passes/update.rs index 6444f4bb9..b6c66ef69 100644 --- a/masonry/src/passes/update.rs +++ b/masonry/src/passes/update.rs @@ -5,11 +5,11 @@ use std::collections::HashSet; use cursor_icon::CursorIcon; use tracing::{info_span, trace}; +use tree_arena::ArenaMut; use crate::passes::event::{run_on_pointer_event_pass, run_on_text_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, TextEvent, Update, UpdateCtx, Widget, WidgetId, WidgetState, diff --git a/masonry/src/render_root.rs b/masonry/src/render_root.rs index c6c6a6114..623d2f31a 100644 --- a/masonry/src/render_root.rs +++ b/masonry/src/render_root.rs @@ -7,6 +7,7 @@ use accesskit::{ActionRequest, TreeUpdate}; use parley::fontique::{self, Collection, CollectionOptions}; use parley::{FontContext, LayoutContext}; use tracing::{info_span, warn}; +use tree_arena::{ArenaMut, TreeArena}; use vello::kurbo::{self, Rect}; use vello::Scene; use winit::window::ResizeDirection; @@ -35,7 +36,6 @@ 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}; diff --git a/masonry/src/widget/widget_arena.rs b/masonry/src/widget/widget_arena.rs index 54842e248..604cd17ed 100644 --- a/masonry/src/widget/widget_arena.rs +++ b/masonry/src/widget/widget_arena.rs @@ -1,7 +1,8 @@ // Copyright 2024 the Xilem Authors // SPDX-License-Identifier: Apache-2.0 -use crate::tree_arena::{ArenaMut, ArenaRef, TreeArena}; +use tree_arena::{ArenaMut, ArenaRef, TreeArena}; + use crate::{Widget, WidgetId, WidgetState}; pub(crate) struct WidgetArena { diff --git a/tree_arena/Cargo.toml b/tree_arena/Cargo.toml new file mode 100644 index 000000000..1b3fd0a4c --- /dev/null +++ b/tree_arena/Cargo.toml @@ -0,0 +1,34 @@ +[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 + + +[package.metadata.docs.rs] +all-features = true # all features are enabled - this means that the generated docs are for the safe version +# There are no platform specific docs. +default-target = "x86_64-unknown-linux-gnu" +targets = [] +# rustdoc-scrape-examples tracking issue https://github.com/rust-lang/rust/issues/88791 +# cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] # no examples yet + + +[dependencies] + +[features] +# This crate contains two implementations of a tree for use in masonry, one safe and the other unsafe. +# The safe tree is known to work, and serves as the baseline implementation and is used by default. +# The unsafe tree leverages a hashmap as an arena and is designed for higher performance: it leverages unsafe code to achieve this. +# The unsafe tree is not yet fully tested, and is not used by default. +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..675401701 --- /dev/null +++ b/tree_arena/README.md @@ -0,0 +1,49 @@ +# Tree Arena + +This crate contains two implementations of a tree for use in masonry, one safe and the other unsafe. The safe tree is known to work, and serves as the baseline implementation and is used by default. The unsafe tree leverages a hashmap as an arena and is designed for higher performance: it leverages unsafe code to achieve this. The unsafe tree is not yet fully tested, and is not used by default. + +The safe tree is the priority. This means: + +* The safe version may have features / APIs that the unsafe version doesn't yet have. + +* If both versions are at feature parity, Masonry can switch on the unsafe version for best performance. + +* Otherwise, Masonry uses the safe version. + +## Architecture + +### Safe Tree + +The safe tree contains a root `TreeArena` which owns the root nodes as `Vec>`, and a`parents_map` tracking the parent of every node. Each `TreeNode` subsequently owns its own children as `Vec>`. This model of owneship is thus checked by the rust compiler, but has the downside of requiring passing through every ancestor node to access the descendant - this requires an O(depth) determination of whether the node is a descendant, followed by O(children) time at each level to traverse the path to the child. + +### Unsafe Tree + +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. We do this by leveraging a hash map to store the nodes: from this we can obtain either shared or exclusive access to nodes. To ensure that only one item is allowed to create new exclusive access to nodes, this action requires mutable access to the arena as a whole (and so is checked by the compiler) - what the compiler cannot check is that the nodes accessed mutably are distinct from one another - this is done by only allowing access to descendants of the node being accessed mutably. The aim of this is to reduce the time needed to access node, as given a node, we only need to determine whether it is a descendant of the node being accessed mutably, and do not need to iterate over the children and to flatten the overall tree graph into a hash map. + +#### 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 + +|Operation | Safe | Unsafe | +| --- | --- | --- | +|Find child | O(Children) | O(1) | +|Descendant | O(Depth) | O(Depth) | +|From root | O(Depth) | O(1) | diff --git a/tree_arena/src/lib.rs b/tree_arena/src/lib.rs new file mode 100644 index 000000000..069eeeb93 --- /dev/null +++ b/tree_arena/src/lib.rs @@ -0,0 +1,23 @@ +// Copyright 2024 the Xilem Authors +// SPDX-License-Identifier: Apache-2.0 + +//! This crate implements a tree data structure for use in Masonry +//! It contains both a safe implementation (that is used by default) +//! and an unsafe implementation that can be used to improve performance +//! +//! The safe version is the first class citizen +//! +//! * The safe version may have features / APIs that the unsafe version doesn't yet have. +//! * If both versions are at feature parity, Masonry can switch on the unsafe version for best performance. +//! * Otherwise, Masonry uses the safe version. +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 91eb37a7c..71171b432 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 payload of the node + pub item: &'arena T, + /// Reference to 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 payload of the node + pub item: &'arena mut T, + /// Reference to 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 Clone for ArenaRef<'_, Item> { +impl Clone for ArenaRef<'_, T> { fn clone(&self) -> Self { *self } } -impl Copy for ArenaRef<'_, Item> {} +impl Copy for ArenaRef<'_, T> {} -impl Clone for ArenaRefChildren<'_, Item> { +impl Clone for ArenaRefChildren<'_, T> { fn clone(&self) -> Self { *self } } -impl Copy for ArenaRefChildren<'_, 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 ArenaRef<'_, 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 ArenaMut<'_, 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 ArenaMut<'_, 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 ArenaMut<'_, 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,20 +493,16 @@ 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)) } } @@ -495,7 +516,11 @@ impl ArenaMapRef<'_> { /// 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,17 +528,16 @@ impl ArenaMapRef<'_> { } 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 @@ -544,27 +568,7 @@ impl ArenaMapMut<'_> { /// 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..b3c71b271 --- /dev/null +++ b/tree_arena/src/tree_arena_unsafe.rs @@ -0,0 +1,592 @@ +// 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>, +} + +/// 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, + /// The roots of the tree + roots: Vec, +} + +/// 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, +} + +/// 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 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> {} + +impl DataMap { + fn new() -> Self { + Self { + items: HashMap::new(), + parents: HashMap::new(), + } + } + + /// Find an item in the tree. + /// + /// Returns a shared reference to the item if present. + /// + /// Time Complexity O(1) + fn find_inner(&self, id: NodeId) -> Option> { + let parent_id = *self.parents.get(&id)?; + let node_cell = self.items.get(&id)?; + + // 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 TreeNode { item, .. } = unsafe { node_cell.get().as_ref()? }; + + let children = ArenaRefChildren { + parent_arena: self, + id: Some(id), + }; + + Some(ArenaRef { + parent_id, + item, + children, + }) + } + + /// Find an item in the tree. + /// + /// Returns a mutable reference to the item if present. + /// + /// Time Complexity O(1) + fn find_mut_inner(&mut self, id: NodeId) -> Option> { + let parent_id = *self.parents.get(&id)?; + let node_cell = self.items.get(&id)?; + + // 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 + // + // Similarly we cannot take any other actions that would affect this node, + // such as removing it or removing a parent (and thus this node) or violate + // exclusivity by creating a shared reference to the node + let TreeNode { item, children } = unsafe { node_cell.get().as_mut()? }; + + let children = ArenaMutChildren { + parent_arena: self, + id: Some(id), + child_arr: children, + }; + + Some(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: NodeId, start_id: Option) -> Vec { + 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(), + roots: Vec::new(), + } + } + + /// Returns a handle whose children are the roots, if any, of the tree. + pub fn root_token(&self) -> ArenaRefChildren<'_, T> { + ArenaRefChildren { + parent_arena: &self.data_map, + id: None, + } + } + + /// 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. + 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 = &mut self.roots; + 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_inner(id.into()) + } + + /// 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 + self.data_map.find_mut_inner(id.into()) + } + + /// 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.into(), 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 { + if self.parent_arena.items.contains_key(&id) { + // 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() { + return true; + } + + // iff the path is empty, there is no path from id to self + !self.parent_arena.get_id_path(id, parent_id).is_empty() + } else { + // if the id is not in the tree, it is not a descendant + false + } + } + + /// 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_inner(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_inner(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). except access from root which is 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_inner(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_inner(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 + self.parent_arena.find_mut_inner(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_inner(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 + self.parent_arena.find_mut_inner(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, + } + } + + /// 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). except access from root which is O(1). + pub fn find(self, id: impl Into) -> Option> { + let id = id.into(); + if self.is_descendant(id) { + self.parent_arena.find_inner(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). except access from root which is 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 + self.parent_arena.find_mut_inner(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..5e1d63544 --- /dev/null +++ b/tree_arena/tests/basic_tests.rs @@ -0,0 +1,135 @@ +// Copyright 2024 the Xilem Authors +// SPDX-License-Identifier: Apache-2.0 + +use std::mem; + +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 creating trees- +// --1(a)--2(b) +// | +// 3(c)--4(d) +// +// and +// +// --4(e)--3(f) +// | +// 2(g)--1(h) +// +// and swapping references to the children of 1(a) and 4(e) +#[test] +fn mem_swap() { + let mut tree_a: TreeArena = TreeArena::new(); + let mut roots_a = tree_a.root_token_mut(); + roots_a.insert_child(1_u64, 'a'); + let mut node_1_a = roots_a.get_child_mut(1_u64).expect("No child 1 found"); + node_1_a.children.insert_child(2_u64, 'b'); + node_1_a.children.insert_child(3_u64, 'c'); + let mut node_3_a = node_1_a + .children + .get_child_mut(3_u64) + .expect("No child 3 found"); + + node_3_a.children.insert_child(4_u64, 'd'); + + let mut tree_b: TreeArena = TreeArena::new(); + let mut roots_b = tree_b.root_token_mut(); + roots_b.insert_child(4_u64, 'e'); + let mut node_4_b = roots_b.get_child_mut(4_u64).expect("No child 4 found"); + node_4_b.children.insert_child(3_u64, 'f'); + node_4_b.children.insert_child(2_u64, 'g'); + let mut node_2_b = node_4_b + .children + .get_child_mut(2_u64) + .expect("No child 2 found"); + node_2_b.children.insert_child(1_u64, 'h'); + + mem::swap(&mut node_1_a.children, &mut node_4_b.children); + + // node 1 from tree a now believes it is node 4 from tree b + assert_eq!(node_1_a.id(), 4_u64, "Node 1 id should be 4"); + // however it still contains the item from tree a + assert_eq!(*node_1_a.item, 'a', "Node 1 item should be 'a'"); + // and we can access the nodes in tree b, that node 4 was able to + assert_eq!( + *node_1_a.children.get_child(2_u64).unwrap().item, + 'g', + "Node 2 item in tree b should be g" + ); +}