diff --git a/src/error/error.rs b/src/error/error.rs index 41a30cf..f4d1a53 100644 --- a/src/error/error.rs +++ b/src/error/error.rs @@ -3,6 +3,9 @@ use std::num::TryFromIntError; use std::str::Utf8Error; use std::string::FromUtf8Error; +use crate::nix_util::ActivityIdAlreadyInTreeError; +use crate::nix_util::ActivityIdNotInTreeError; + #[derive(Debug)] pub(crate) enum CustomError { Static(#[allow(dead_code)] &'static str), @@ -19,6 +22,8 @@ pub(crate) enum CustomError { Migrate(#[allow(dead_code)] sqlx::migrate::MigrateError), Sql(#[allow(dead_code)] sqlx::Error), TryFromIntError(#[allow(dead_code)] TryFromIntError), + ActivityIdNotInTreeError(#[allow(dead_code)] ActivityIdNotInTreeError), + ActivityIdAlreadyInTreeError(#[allow(dead_code)] ActivityIdAlreadyInTreeError), } impl Display for CustomError { @@ -110,3 +115,15 @@ impl From for CustomError { CustomError::TryFromIntError(value) } } + +impl From for CustomError { + fn from(value: ActivityIdNotInTreeError) -> Self { + CustomError::ActivityIdNotInTreeError(value) + } +} + +impl From for CustomError { + fn from(value: ActivityIdAlreadyInTreeError) -> Self { + CustomError::ActivityIdAlreadyInTreeError(value) + } +} diff --git a/src/nix_util/activity.rs b/src/nix_util/activity.rs new file mode 100644 index 0000000..484a2bc --- /dev/null +++ b/src/nix_util/activity.rs @@ -0,0 +1,284 @@ +use std::borrow::Cow; + +use tracing::warn; + +#[repr(u8)] +pub(crate) enum Activity { + Root(ActivityRoot), + Unknown(ActivityUnknown), + CopyPath(ActivityCopyPath), + FileTransfer(ActivityFileTransfer), + Realize(ActivityRealize), + CopyPaths(ActivityCopyPaths), + Builds(ActivityBuilds), + Build(ActivityBuild), + OptimizeStore(ActivityOptimizeStore), + VerifyPaths(ActivityVerifyPaths), + Substitute(ActivitySubstitute), + QueryPathInfo(ActivityQueryPathInfo), + PostBuildHook(ActivityPostBuildHook), + BuildWaiting(ActivityBuildWaiting), + FetchTree(ActivityFetchTree), +} + +impl Activity { + pub(crate) fn stop(&mut self) -> () { + match self { + Activity::Root(_activity_root) => { + panic!("Attempted to start root activity."); + } + Activity::Unknown(activity_unknown) => { + activity_unknown.state = ActivityState::Stopped; + } + Activity::CopyPath(activity_copy_path) => { + activity_copy_path.state = ActivityState::Stopped; + } + Activity::FileTransfer(activity_file_transfer) => { + activity_file_transfer.state = ActivityState::Stopped; + } + Activity::Realize(activity_realize) => { + activity_realize.state = ActivityState::Stopped; + } + Activity::CopyPaths(activity_copy_paths) => { + activity_copy_paths.state = ActivityState::Stopped; + } + Activity::Builds(activity_builds) => { + activity_builds.state = ActivityState::Stopped; + } + Activity::Build(activity_build) => { + activity_build.state = ActivityState::Stopped; + } + Activity::OptimizeStore(activity_optimize_store) => { + activity_optimize_store.state = ActivityState::Stopped; + } + Activity::VerifyPaths(activity_verify_paths) => { + activity_verify_paths.state = ActivityState::Stopped; + } + Activity::Substitute(activity_substitute) => { + activity_substitute.state = ActivityState::Stopped; + } + Activity::QueryPathInfo(activity_query_path_info) => { + activity_query_path_info.state = ActivityState::Stopped; + } + Activity::PostBuildHook(activity_post_build_hook) => { + activity_post_build_hook.state = ActivityState::Stopped; + } + Activity::BuildWaiting(activity_build_waiting) => { + activity_build_waiting.state = ActivityState::Stopped; + } + Activity::FetchTree(activity_fetch_tree) => { + activity_fetch_tree.state = ActivityState::Stopped; + } + } + } + + pub(crate) fn is_active(&self) -> bool { + match self { + Activity::Root(_activity_root) => true, + Activity::Unknown(activity_unknown) => { + matches!(activity_unknown.state, ActivityState::Started { .. }) + } + Activity::CopyPath(activity_copy_path) => { + matches!(activity_copy_path.state, ActivityState::Started { .. }) + } + Activity::FileTransfer(activity_file_transfer) => { + matches!(activity_file_transfer.state, ActivityState::Started { .. }) + } + Activity::Realize(activity_realize) => { + matches!(activity_realize.state, ActivityState::Started { .. }) + } + Activity::CopyPaths(activity_copy_paths) => { + matches!(activity_copy_paths.state, ActivityState::Started { .. }) + } + Activity::Builds(activity_builds) => { + matches!(activity_builds.state, ActivityState::Started { .. }) + } + Activity::Build(activity_build) => { + matches!(activity_build.state, ActivityState::Started { .. }) + } + Activity::OptimizeStore(activity_optimize_store) => { + matches!(activity_optimize_store.state, ActivityState::Started { .. }) + } + Activity::VerifyPaths(activity_verify_paths) => { + matches!(activity_verify_paths.state, ActivityState::Started { .. }) + } + Activity::Substitute(activity_substitute) => { + matches!(activity_substitute.state, ActivityState::Started { .. }) + } + Activity::QueryPathInfo(activity_query_path_info) => { + matches!( + activity_query_path_info.state, + ActivityState::Started { .. } + ) + } + Activity::PostBuildHook(activity_post_build_hook) => { + matches!( + activity_post_build_hook.state, + ActivityState::Started { .. } + ) + } + Activity::BuildWaiting(activity_build_waiting) => { + matches!(activity_build_waiting.state, ActivityState::Started { .. }) + } + Activity::FetchTree(activity_fetch_tree) => { + matches!(activity_fetch_tree.state, ActivityState::Started { .. }) + } + } + } + + pub(crate) fn is_transparent(&self) -> bool { + match self { + Activity::Root(_activity_root) => true, + Activity::Unknown(_activity_unknown) => false, + Activity::CopyPath(_activity_copy_path) => false, + Activity::FileTransfer(_activity_file_transfer) => false, + Activity::Realize(_activity_realize) => true, + Activity::CopyPaths(_activity_copy_paths) => true, + Activity::Builds(_activity_builds) => true, + Activity::Build(_activity_build) => false, + Activity::OptimizeStore(_activity_optimize_store) => false, + Activity::VerifyPaths(_activity_verify_paths) => false, + Activity::Substitute(_activity_substitute) => false, + Activity::QueryPathInfo(_activity_query_path_info) => false, + Activity::PostBuildHook(_activity_post_build_hook) => false, + Activity::BuildWaiting(_activity_build_waiting) => true, + Activity::FetchTree(_activity_fetch_tree) => false, + } + } + + pub(crate) fn display_name(&self) -> Option> { + match self { + Activity::Root(_activity_root) => { + warn!("Unexpected display of root activity."); + Some(Cow::Borrowed("Root")) + } + Activity::Unknown(activity_unknown) => { + Some(Cow::Owned(format!("Unknown({})", activity_unknown.text))) + } + Activity::CopyPath(activity_copy_path) => Some(Cow::Owned(format!( + "CopyPath({} | {} -> {})", + activity_copy_path.missing_path, + activity_copy_path.source, + activity_copy_path.destination + ))), + Activity::FileTransfer(activity_file_transfer) => Some(Cow::Owned(format!( + "FileTransfer({})", + activity_file_transfer.url + ))), + Activity::Realize(_activity_realize) => Some(Cow::Borrowed("Realize")), + Activity::CopyPaths(activity_copy_paths) => Some(Cow::Owned(format!( + "CopyPaths({})", + activity_copy_paths.text + ))), + Activity::Builds(_activity_builds) => Some(Cow::Borrowed("Builds")), + Activity::Build(activity_build) => { + if let Some(machine_name) = &activity_build.machine_name { + Some(Cow::Owned(format!( + "Build({}@{machine_name})", + activity_build.drv_path + ))) + } else { + Some(Cow::Owned(format!("Build({})", activity_build.drv_path))) + } + } + Activity::OptimizeStore(_activity_optimize_store) => { + Some(Cow::Borrowed("OptimizeStore")) + } + Activity::VerifyPaths(_activity_verify_paths) => Some(Cow::Borrowed("VerifyPaths")), + Activity::Substitute(_activity_substitute) => Some(Cow::Borrowed("Substitute")), + Activity::QueryPathInfo(_activity_query_path_info) => { + Some(Cow::Borrowed("QueryPathInfo")) + } + Activity::PostBuildHook(_activity_post_build_hook) => { + Some(Cow::Borrowed("PostBuildHook")) + } + Activity::BuildWaiting(activity_build_waiting) => Some(Cow::Owned(format!( + "BuildWaiting({:?} | {:?} | {})", + activity_build_waiting.drv_path, + activity_build_waiting.path_resolved, + activity_build_waiting.text + ))), + Activity::FetchTree(_activity_fetch_tree) => Some(Cow::Borrowed("FetchTree")), + } + } +} + +pub(crate) struct ActivityRoot {} +pub(crate) struct ActivityUnknown { + pub(crate) state: ActivityState, + pub(crate) text: String, +} +pub(crate) struct ActivityCopyPath { + pub(crate) state: ActivityState, + pub(crate) missing_path: String, + + /// The machine with the file(s) + pub(crate) source: String, + + /// The machine that is receiving the file(s) + pub(crate) destination: String, +} +pub(crate) struct ActivityFileTransfer { + pub(crate) state: ActivityState, + pub(crate) url: String, +} +pub(crate) struct ActivityRealize { + pub(crate) state: ActivityState, +} +pub(crate) struct ActivityCopyPaths { + pub(crate) state: ActivityState, + pub(crate) text: String, +} +pub(crate) struct ActivityBuilds { + pub(crate) state: ActivityState, +} +pub(crate) struct ActivityBuild { + pub(crate) state: ActivityState, + pub(crate) drv_path: String, + pub(crate) machine_name: Option, +} +pub(crate) struct ActivityOptimizeStore { + pub(crate) state: ActivityState, +} +pub(crate) struct ActivityVerifyPaths { + pub(crate) state: ActivityState, +} +pub(crate) struct ActivitySubstitute { + pub(crate) state: ActivityState, +} +pub(crate) struct ActivityQueryPathInfo { + pub(crate) state: ActivityState, +} +pub(crate) struct ActivityPostBuildHook { + pub(crate) state: ActivityState, +} +pub(crate) struct ActivityBuildWaiting { + pub(crate) state: ActivityState, + pub(crate) text: String, + pub(crate) drv_path: Option, + pub(crate) path_resolved: Option, +} +pub(crate) struct ActivityFetchTree { + pub(crate) state: ActivityState, +} + +pub(crate) enum ActivityState { + Started { + progress_numerator: u64, + progress_denominator: u64, + progress_running: u64, + progress_failed: u64, + }, + Stopped, +} + +impl Default for ActivityState { + fn default() -> Self { + ActivityState::Started { + progress_numerator: 0, + progress_denominator: 0, + progress_running: 0, + progress_failed: 0, + } + } +} diff --git a/src/nix_util/activity_tree.rs b/src/nix_util/activity_tree.rs new file mode 100644 index 0000000..2f1b909 --- /dev/null +++ b/src/nix_util/activity_tree.rs @@ -0,0 +1,180 @@ +use crate::Result; +use std::collections::BTreeMap; +use std::fmt::Display; +use std::ops::Deref; + +use super::activity::Activity; +use super::activity::ActivityRoot; + +#[derive(Debug, Clone)] +pub(crate) struct ActivityId { + id: u64, +} + +impl ActivityId { + fn new(id: u64) -> ActivityId { + ActivityId { id } + } +} + +impl Deref for ActivityId { + type Target = u64; + + fn deref(&self) -> &Self::Target { + &self.id + } +} + +impl Display for ActivityId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(&**self, f) + } +} + +pub(crate) struct ActivityTree { + activities: BTreeMap, +} + +impl ActivityTree { + pub(crate) fn new() -> ActivityTree { + let mut activities = BTreeMap::new(); + activities.insert( + 0, + ActivityTreeEntry { + id: ActivityId::new(0), + parent_id: ActivityId::new(0), + child_ids: Vec::new(), + activity: Activity::Root(ActivityRoot {}), + }, + ); + ActivityTree { activities } + } + + pub(crate) fn add_activity( + &mut self, + activity_id: u64, + parent_id: u64, + activity: Activity, + ) -> Result<&ActivityTreeEntry> { + // The activity_id is not yet in the tree, so we can't use get_activity_id. + let activity_id = ActivityId::new(activity_id); + let parent_id = self.get_activity_id(parent_id)?; + match self.activities.entry(activity_id.id) { + std::collections::btree_map::Entry::Vacant(vacant_entry) => { + vacant_entry.insert(ActivityTreeEntry { + id: activity_id.clone(), + parent_id: parent_id.clone(), + child_ids: Vec::new(), + activity, + }); + } + std::collections::btree_map::Entry::Occupied(_occupied_entry) => { + return Err(ActivityIdAlreadyInTreeError::new(*activity_id).into()); + } + }; + self.get_mut(&parent_id).child_ids.push(activity_id.clone()); + let tree_entry = self + .activities + .get(&*activity_id) + .expect("We just created this entry so it must exist."); + + Ok(tree_entry) + } + + pub(crate) fn get_activity_id(&self, activity_id: u64) -> Result { + if activity_id == 0 || self.activities.contains_key(&activity_id) { + Ok(ActivityId::new(activity_id)) + } else { + Err(ActivityIdNotInTreeError::new(activity_id).into()) + } + } + + pub(crate) fn get_root_id(&self) -> ActivityId { + // The root always exists, so there is no need to check. + ActivityId::new(0) + } + + pub(crate) fn get(&self, activity_id: &ActivityId) -> &ActivityTreeEntry { + self.activities + .get(activity_id) + .expect("You cannot create an ActivityId if the activity is not in the tree.") + } + + pub(crate) fn get_mut(&mut self, activity_id: &ActivityId) -> &mut ActivityTreeEntry { + self.activities + .get_mut(activity_id) + .expect("You cannot create an ActivityId if the activity is not in the tree.") + } +} + +pub(crate) struct ActivityTreeEntry { + id: ActivityId, + parent_id: ActivityId, + child_ids: Vec, + activity: Activity, +} + +impl ActivityTreeEntry { + pub(crate) fn get_activity_id(&self) -> &ActivityId { + &self.id + } + + pub(crate) fn get_parent_id(&self) -> &ActivityId { + &self.parent_id + } + + pub(crate) fn get_activity(&self) -> &Activity { + &self.activity + } + + pub(crate) fn get_mut_activity(&mut self) -> &mut Activity { + &mut self.activity + } + + pub(crate) fn get_child_ids(&self) -> &Vec { + &self.child_ids + } + + pub(crate) fn iter_children<'tree>( + &'_ self, + activity_tree: &'tree ActivityTree, + ) -> impl Iterator { + self.child_ids + .iter() + .map(|child_id| activity_tree.get(child_id)) + } +} + +#[derive(Debug, Clone)] +pub(crate) struct ActivityIdNotInTreeError { + id: u64, +} + +impl ActivityIdNotInTreeError { + fn new(id: u64) -> ActivityIdNotInTreeError { + ActivityIdNotInTreeError { id } + } +} + +impl std::fmt::Display for ActivityIdNotInTreeError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Activity id {} not in the tree.", self.id) + } +} + +#[derive(Debug, Clone)] +pub(crate) struct ActivityIdAlreadyInTreeError { + id: u64, +} + +impl ActivityIdAlreadyInTreeError { + fn new(id: u64) -> ActivityIdAlreadyInTreeError { + ActivityIdAlreadyInTreeError { id } + } +} + +impl std::fmt::Display for ActivityIdAlreadyInTreeError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Activity id {} already in the tree.", self.id) + } +} diff --git a/src/nix_util/mod.rs b/src/nix_util/mod.rs index caaf31b..b5d1386 100644 --- a/src/nix_util/mod.rs +++ b/src/nix_util/mod.rs @@ -1,7 +1,12 @@ +mod activity; +mod activity_tree; mod high_level; mod nix_output_stream; mod output_stream; mod running_build; +mod transparent_iter; +pub(crate) use activity_tree::ActivityIdAlreadyInTreeError; +pub(crate) use activity_tree::ActivityIdNotInTreeError; pub(crate) use high_level::*; pub(crate) use nix_output_stream::NixOutputStream; pub(crate) use output_stream::OutputLine; diff --git a/src/nix_util/nix_output_stream.rs b/src/nix_util/nix_output_stream.rs index 633e044..b1aa1d1 100644 --- a/src/nix_util/nix_output_stream.rs +++ b/src/nix_util/nix_output_stream.rs @@ -722,6 +722,8 @@ pub(crate) struct ActivityStartCopyPaths { pub(crate) id: u64, pub(crate) parent: u64, pub(crate) level: u8, + + /// Seems to either be blank or "copying 9 paths" pub(crate) text: String, } diff --git a/src/nix_util/running_build.rs b/src/nix_util/running_build.rs index ec50b37..1821b48 100644 --- a/src/nix_util/running_build.rs +++ b/src/nix_util/running_build.rs @@ -1,11 +1,6 @@ -use std::borrow::Cow; -use std::collections::BTreeMap; -use std::collections::BTreeSet; - use sqlx::Row; use tokio::process::Child; use tracing::error; -use tracing::warn; use crate::Result; use crate::database::db_handle::DbHandle; @@ -13,20 +8,40 @@ use crate::nix_util::nix_output_stream::ActivityStartMessage; use crate::nix_util::nix_output_stream::NixAction; use crate::nix_util::nix_output_stream::NixOutputStream; use crate::nix_util::output_stream::OutputStream; +use crate::nix_util::transparent_iter::TransparentIter; +use super::activity::Activity; +use super::activity::ActivityBuild; +use super::activity::ActivityBuildWaiting; +use super::activity::ActivityBuilds; +use super::activity::ActivityCopyPath; +use super::activity::ActivityCopyPaths; +use super::activity::ActivityFetchTree; +use super::activity::ActivityFileTransfer; +use super::activity::ActivityOptimizeStore; +use super::activity::ActivityPostBuildHook; +use super::activity::ActivityQueryPathInfo; +use super::activity::ActivityRealize; +use super::activity::ActivityState; +use super::activity::ActivitySubstitute; +use super::activity::ActivityUnknown; +use super::activity::ActivityVerifyPaths; +use super::activity_tree::ActivityId; +use super::activity_tree::ActivityTree; +use super::activity_tree::ActivityTreeEntry; use super::nix_output_stream::ActivityResultMessage; use super::nix_output_stream::NixMessage; pub(crate) struct RunningBuild<'db> { db_handle: &'db DbHandle, - activity_tree: BTreeMap, + activity_tree: ActivityTree, } impl<'db> RunningBuild<'db> { pub(crate) fn new(db_handle: &'db DbHandle) -> Result { Ok(RunningBuild { db_handle, - activity_tree: BTreeMap::new(), + activity_tree: ActivityTree::new(), }) } @@ -98,135 +113,157 @@ impl<'db> RunningBuild<'db> { // For now we can ignore the messages. } NixAction::Start(activity_start_message) => { - let id = activity_start_message.get_id(); - let entry = self.activity_tree.entry(id); - let entry = match entry { - std::collections::btree_map::Entry::Vacant(vacant_entry) => vacant_entry, - std::collections::btree_map::Entry::Occupied(_occupied_entry) => { - panic!("Started an already started activity: {id}.") - } - }; match activity_start_message { ActivityStartMessage::Unknown(activity_start_unknown) => { - entry.insert(Activity::Unknown(ActivityUnknown { - id: activity_start_unknown.id, - parent: activity_start_unknown.parent, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_unknown.id, + activity_start_unknown.parent, + Activity::Unknown(ActivityUnknown { + state: ActivityState::default(), + text: activity_start_unknown.text, + }), + )?; } ActivityStartMessage::CopyPath(activity_start_copy_path) => { - entry.insert(Activity::CopyPath(ActivityCopyPath { - id: activity_start_copy_path.id, - parent: activity_start_copy_path.parent, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_copy_path.id, + activity_start_copy_path.parent, + Activity::CopyPath(ActivityCopyPath { + state: ActivityState::default(), + missing_path: activity_start_copy_path.missing_path, + source: activity_start_copy_path.source, + destination: activity_start_copy_path.destination, + }), + )?; } ActivityStartMessage::FileTransfer(activity_start_file_transfer) => { - entry.insert(Activity::FileTransfer(ActivityFileTransfer { - id: activity_start_file_transfer.id, - parent: activity_start_file_transfer.parent, - state: ActivityState::default(), - url: activity_start_file_transfer.url, - })); + self.activity_tree.add_activity( + activity_start_file_transfer.id, + activity_start_file_transfer.parent, + Activity::FileTransfer(ActivityFileTransfer { + state: ActivityState::default(), + url: activity_start_file_transfer.url, + }), + )?; } ActivityStartMessage::Realize(activity_start_realize) => { - entry.insert(Activity::Realize(ActivityRealize { - id: activity_start_realize.id, - parent: activity_start_realize.parent, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_realize.id, + activity_start_realize.parent, + Activity::Realize(ActivityRealize { + state: ActivityState::default(), + }), + )?; } ActivityStartMessage::CopyPaths(activity_start_copy_paths) => { - entry.insert(Activity::CopyPaths(ActivityCopyPaths { - id: activity_start_copy_paths.id, - parent: activity_start_copy_paths.parent, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_copy_paths.id, + activity_start_copy_paths.parent, + Activity::CopyPaths(ActivityCopyPaths { + state: ActivityState::default(), + text: activity_start_copy_paths.text, + }), + )?; } ActivityStartMessage::Builds(activity_start_builds) => { - entry.insert(Activity::Builds(ActivityBuilds { - id: activity_start_builds.id, - parent: activity_start_builds.parent, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_builds.id, + activity_start_builds.parent, + Activity::Builds(ActivityBuilds { + state: ActivityState::default(), + }), + )?; } ActivityStartMessage::Build(activity_start_build) => { - entry.insert(Activity::Build(ActivityBuild { - id: activity_start_build.id, - parent: activity_start_build.parent, - state: ActivityState::default(), - drv_path: activity_start_build.drv_path, - machine_name: if activity_start_build.machine_name.len() > 0 { - Some(activity_start_build.machine_name) - } else { - None - }, - })); + self.activity_tree.add_activity( + activity_start_build.id, + activity_start_build.parent, + Activity::Build(ActivityBuild { + state: ActivityState::default(), + drv_path: activity_start_build.drv_path, + machine_name: if activity_start_build.machine_name.len() > 0 { + Some(activity_start_build.machine_name) + } else { + None + }, + }), + )?; } ActivityStartMessage::OptimizeStore(activity_start_optimize_store) => { - entry.insert(Activity::OptimizeStore(ActivityOptimizeStore { - id: activity_start_optimize_store.id, - parent: activity_start_optimize_store.parent, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_optimize_store.id, + activity_start_optimize_store.parent, + Activity::OptimizeStore(ActivityOptimizeStore { + state: ActivityState::default(), + }), + )?; } ActivityStartMessage::VerifyPaths(activity_start_verify_paths) => { - entry.insert(Activity::VerifyPaths(ActivityVerifyPaths { - id: activity_start_verify_paths.id, - parent: activity_start_verify_paths.parent, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_verify_paths.id, + activity_start_verify_paths.parent, + Activity::VerifyPaths(ActivityVerifyPaths { + state: ActivityState::default(), + }), + )?; } ActivityStartMessage::Substitute(activity_start_substitute) => { - entry.insert(Activity::Substitute(ActivitySubstitute { - id: activity_start_substitute.id, - parent: activity_start_substitute.parent, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_substitute.id, + activity_start_substitute.parent, + Activity::Substitute(ActivitySubstitute { + state: ActivityState::default(), + }), + )?; } ActivityStartMessage::QueryPathInfo(activity_start_query_path_info) => { - entry.insert(Activity::QueryPathInfo(ActivityQueryPathInfo { - id: activity_start_query_path_info.id, - parent: activity_start_query_path_info.parent, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_query_path_info.id, + activity_start_query_path_info.parent, + Activity::QueryPathInfo(ActivityQueryPathInfo { + state: ActivityState::default(), + }), + )?; } ActivityStartMessage::PostBuildHook(activity_start_post_build_hook) => { - entry.insert(Activity::PostBuildHook(ActivityPostBuildHook { - id: activity_start_post_build_hook.id, - parent: activity_start_post_build_hook.parent, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_post_build_hook.id, + activity_start_post_build_hook.parent, + Activity::PostBuildHook(ActivityPostBuildHook { + state: ActivityState::default(), + }), + )?; } ActivityStartMessage::BuildWaiting(activity_start_build_waiting) => { - entry.insert(Activity::BuildWaiting(ActivityBuildWaiting { - id: activity_start_build_waiting.id, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_build_waiting.id, + 0, + Activity::BuildWaiting(ActivityBuildWaiting { + state: ActivityState::default(), + text: activity_start_build_waiting.text, + drv_path: activity_start_build_waiting.drv_path, + path_resolved: activity_start_build_waiting.path_resolved, + }), + )?; } ActivityStartMessage::FetchTree(activity_start_fetch_tree) => { - entry.insert(Activity::FetchTree(ActivityFetchTree { - id: activity_start_fetch_tree.id, - parent: activity_start_fetch_tree.parent, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_fetch_tree.id, + activity_start_fetch_tree.parent, + Activity::FetchTree(ActivityFetchTree { + state: ActivityState::default(), + }), + )?; } }; self.print_current_status(); } NixAction::Stop(stop_message) => { - let entry = self.activity_tree.entry(stop_message.id); - match entry { - std::collections::btree_map::Entry::Vacant(_vacant_entry) => { - panic!( - "Stopped an activity that is not in the tree: {}", - stop_message.id - ); - } - std::collections::btree_map::Entry::Occupied(mut occupied_entry) => { - occupied_entry.get_mut().stop(); - } - }; + let activity = self + .activity_tree + .get_activity_id(stop_message.id) + .map(|activity_id| self.activity_tree.get_mut(&activity_id))?; + activity.get_mut_activity().stop(); self.print_current_status(); // println!("{}", serde_json::to_string(&message)?); } @@ -302,11 +339,9 @@ impl<'db> RunningBuild<'db> { } else { "𜸛" }; - let activity = self - .activity_tree - .get(&dag_entry.activity_id) - .expect("Activity should exist in the tree."); + let activity = self.activity_tree.get(&dag_entry.activity_id); let display_name = activity + .get_activity() .display_name() .expect("Currently we always return a display name."); let activity_id = activity.get_activity_id(); @@ -321,22 +356,13 @@ impl<'db> RunningBuild<'db> { } fn get_draw_order(&self) -> Vec { - let active_activity_ids: BTreeSet = self - .activity_tree - .iter() - .filter(|(activity_id, activity)| activity.is_active()) - .flat_map(|(activity_id, activity)| { - PathToRootIter::new(&self.activity_tree, *activity_id) - }) - .collect(); - let mut draw_order: Vec = Vec::new(); let mut stack: Vec = self - .get_children_in_order(0) - .filter_map(|child_id| { - if active_activity_ids.contains(&child_id) { + .get_children_in_order(self.activity_tree.get_root_id()) + .filter_map(|child| { + if child.get_activity().is_active() { Some(DrawStackEntry::HasNotVisitedChildren(DrawDagEntry { - activity_id: child_id, + activity_id: child.get_activity_id().clone(), depth: Vec::new(), has_later_siblings: true, })) @@ -357,14 +383,19 @@ impl<'db> RunningBuild<'db> { let current_entry = stack.pop().expect("While-loop ensured this is Some."); match current_entry { DrawStackEntry::HasNotVisitedChildren(draw_dag_entry) => { - let current_id = draw_dag_entry.activity_id; + let current_id = draw_dag_entry.activity_id.clone(); let mut current_depth = draw_dag_entry.depth.clone(); current_depth.push(draw_dag_entry.has_later_siblings); stack.push(DrawStackEntry::VisitedChildren(draw_dag_entry)); - let children: Vec = self + let children: Vec = self .get_children_in_order(current_id) - .into_iter() - .filter(|child_id| active_activity_ids.contains(child_id)) + .filter_map(|child| { + if child.get_activity().is_active() { + Some(child.get_activity_id().clone()) + } else { + None + } + }) .collect(); let has_children = !children.is_empty(); stack.extend(children.into_iter().map(|child_id| { @@ -389,35 +420,16 @@ impl<'db> RunningBuild<'db> { draw_order } - fn get_children_in_order(&self, parent_id: u64) -> impl Iterator { - let child_ids = self - .activity_tree + fn get_children_in_order( + &self, + parent_id: ActivityId, + ) -> impl Iterator { + let parent = self.activity_tree.get(&parent_id); + parent + .get_child_ids() .iter() - .filter(move |(_child_id, activity)| activity.get_parent_id() == parent_id) - .flat_map(|(_child_id, activity)| TransparentIter::new(&self.activity_tree, activity)) - .map(|activity| activity.get_activity_id()); - child_ids - } -} - -enum ActivityState { - Started { - progress_numerator: u64, - progress_denominator: u64, - progress_running: u64, - progress_failed: u64, - }, - Stopped, -} - -impl Default for ActivityState { - fn default() -> Self { - ActivityState::Started { - progress_numerator: 0, - progress_denominator: 0, - progress_running: 0, - progress_failed: 0, - } + .map(|child_id| self.activity_tree.get(child_id)) + .flat_map(|child| TransparentIter::new(&self.activity_tree, child)) } } @@ -427,7 +439,7 @@ enum DrawStackEntry { } struct DrawDagEntry { - activity_id: u64, + activity_id: ActivityId, depth: Vec, has_later_siblings: bool, } @@ -444,569 +456,3 @@ impl DrawStackEntry { }; } } - -struct PathToRootIter<'tree> { - activity_tree: &'tree BTreeMap, - next_activity_id: u64, -} - -impl<'tree> PathToRootIter<'tree> { - fn new( - activity_tree: &'tree BTreeMap, - next_activity_id: u64, - ) -> PathToRootIter<'tree> { - PathToRootIter { - activity_tree, - next_activity_id, - } - } -} - -impl<'tree> Iterator for PathToRootIter<'tree> { - type Item = u64; - - fn next(&mut self) -> Option { - if self.next_activity_id == 0 { - return None; - } - let next_activity = self.activity_tree.get(&self.next_activity_id).expect( - "There shouldn't be a reference to an activity that does not exist in the tree.", - ); - self.next_activity_id = next_activity.get_parent_id(); - Some(next_activity.get_activity_id()) - } -} - -/// An iterator that returns either the original activity if it is not transparent, or if it is transparent, the earliest non-transparent children. -struct TransparentIter<'tree> { - activity_tree: &'tree BTreeMap, - parent: &'tree Activity, - child_index: usize, -} - -impl<'tree> TransparentIter<'tree> { - fn new( - activity_tree: &'tree BTreeMap, - parent: &'tree Activity, - ) -> TransparentIter<'tree> { - TransparentIter { - activity_tree, - parent, - child_index: 0, - } - } -} - -impl<'tree> Iterator for TransparentIter<'tree> { - type Item = &'tree Activity; - - fn next(&mut self) -> Option { - if self.child_index == usize::max_value() { - return None; - } - if !self.parent.is_transparent() { - self.child_index = usize::max_value(); - return Some(self.parent); - } - - let mut visible_children_remaining = self.child_index; - for (_child_id, child) in self.activity_tree.iter() { - if child.get_parent_id() != self.parent.get_activity_id() { - continue; - } - for visible_child in TransparentIter::new(self.activity_tree, child) { - if visible_children_remaining > 0 { - visible_children_remaining -= 1; - } else { - self.child_index += 1; - return Some(visible_child); - } - } - } - return None; - } -} - -#[repr(u8)] -enum Activity { - Unknown(ActivityUnknown), - CopyPath(ActivityCopyPath), - FileTransfer(ActivityFileTransfer), - Realize(ActivityRealize), - CopyPaths(ActivityCopyPaths), - Builds(ActivityBuilds), - Build(ActivityBuild), - OptimizeStore(ActivityOptimizeStore), - VerifyPaths(ActivityVerifyPaths), - Substitute(ActivitySubstitute), - QueryPathInfo(ActivityQueryPathInfo), - PostBuildHook(ActivityPostBuildHook), - BuildWaiting(ActivityBuildWaiting), - FetchTree(ActivityFetchTree), -} - -impl Activity { - fn stop(&mut self) -> () { - match self { - Activity::Unknown(activity_unknown) => { - activity_unknown.state = ActivityState::Stopped; - } - Activity::CopyPath(activity_copy_path) => { - activity_copy_path.state = ActivityState::Stopped; - } - Activity::FileTransfer(activity_file_transfer) => { - activity_file_transfer.state = ActivityState::Stopped; - } - Activity::Realize(activity_realize) => { - activity_realize.state = ActivityState::Stopped; - } - Activity::CopyPaths(activity_copy_paths) => { - activity_copy_paths.state = ActivityState::Stopped; - } - Activity::Builds(activity_builds) => { - activity_builds.state = ActivityState::Stopped; - } - Activity::Build(activity_build) => { - activity_build.state = ActivityState::Stopped; - } - Activity::OptimizeStore(activity_optimize_store) => { - activity_optimize_store.state = ActivityState::Stopped; - } - Activity::VerifyPaths(activity_verify_paths) => { - activity_verify_paths.state = ActivityState::Stopped; - } - Activity::Substitute(activity_substitute) => { - activity_substitute.state = ActivityState::Stopped; - } - Activity::QueryPathInfo(activity_query_path_info) => { - activity_query_path_info.state = ActivityState::Stopped; - } - Activity::PostBuildHook(activity_post_build_hook) => { - activity_post_build_hook.state = ActivityState::Stopped; - } - Activity::BuildWaiting(activity_build_waiting) => { - activity_build_waiting.state = ActivityState::Stopped; - } - Activity::FetchTree(activity_fetch_tree) => { - activity_fetch_tree.state = ActivityState::Stopped; - } - } - } - - fn is_active(&self) -> bool { - match self { - Activity::Unknown(activity_unknown) => { - matches!(activity_unknown.state, ActivityState::Started { .. }) - } - Activity::CopyPath(activity_copy_path) => { - matches!(activity_copy_path.state, ActivityState::Started { .. }) - } - Activity::FileTransfer(activity_file_transfer) => { - matches!(activity_file_transfer.state, ActivityState::Started { .. }) - } - Activity::Realize(activity_realize) => { - matches!(activity_realize.state, ActivityState::Started { .. }) - } - Activity::CopyPaths(activity_copy_paths) => { - matches!(activity_copy_paths.state, ActivityState::Started { .. }) - } - Activity::Builds(activity_builds) => { - matches!(activity_builds.state, ActivityState::Started { .. }) - } - Activity::Build(activity_build) => { - matches!(activity_build.state, ActivityState::Started { .. }) - } - Activity::OptimizeStore(activity_optimize_store) => { - matches!(activity_optimize_store.state, ActivityState::Started { .. }) - } - Activity::VerifyPaths(activity_verify_paths) => { - matches!(activity_verify_paths.state, ActivityState::Started { .. }) - } - Activity::Substitute(activity_substitute) => { - matches!(activity_substitute.state, ActivityState::Started { .. }) - } - Activity::QueryPathInfo(activity_query_path_info) => { - matches!( - activity_query_path_info.state, - ActivityState::Started { .. } - ) - } - Activity::PostBuildHook(activity_post_build_hook) => { - matches!( - activity_post_build_hook.state, - ActivityState::Started { .. } - ) - } - Activity::BuildWaiting(activity_build_waiting) => { - matches!(activity_build_waiting.state, ActivityState::Started { .. }) - } - Activity::FetchTree(activity_fetch_tree) => { - matches!(activity_fetch_tree.state, ActivityState::Started { .. }) - } - } - } - - fn is_transparent(&self) -> bool { - match self { - Activity::Unknown(_activity_unknown) => false, - Activity::CopyPath(_activity_copy_path) => false, - Activity::FileTransfer(_activity_file_transfer) => false, - Activity::Realize(_activity_realize) => false, - Activity::CopyPaths(_activity_copy_paths) => false, - Activity::Builds(_activity_builds) => false, - Activity::Build(_activity_build) => true, - Activity::OptimizeStore(_activity_optimize_store) => false, - Activity::VerifyPaths(_activity_verify_paths) => false, - Activity::Substitute(_activity_substitute) => false, - Activity::QueryPathInfo(_activity_query_path_info) => false, - Activity::PostBuildHook(_activity_post_build_hook) => false, - Activity::BuildWaiting(_activity_build_waiting) => true, - Activity::FetchTree(_activity_fetch_tree) => false, - } - } - - fn display_name(&self) -> Option> { - match self { - Activity::Unknown(activity_unknown) => { - warn!("Unimplemented display_name Unknown"); - // TODO - Some(Cow::Borrowed("Unknown")) - } - Activity::CopyPath(activity_copy_path) => { - warn!("Unimplemented display_name CopyPath"); - // TODO - Some(Cow::Borrowed("CopyPath")) - } - Activity::FileTransfer(activity_file_transfer) => Some(Cow::Owned(format!( - "FileTransfer({})", - activity_file_transfer.url - ))), - Activity::Realize(activity_realize) => { - // TODO what is a good display name for this? Maybe Realize() ? - Some(Cow::Borrowed("Realize")) - } - Activity::CopyPaths(activity_copy_paths) => { - warn!("Unimplemented display_name CopyPaths"); - // TODO - Some(Cow::Borrowed("CopyPaths")) - } - Activity::Builds(activity_builds) => { - warn!("Unimplemented display_name Builds"); - // TODO - Some(Cow::Borrowed("Builds")) - } - Activity::Build(activity_build) => { - if let Some(machine_name) = &activity_build.machine_name { - Some(Cow::Owned(format!( - "Build({}@{machine_name})", - activity_build.drv_path - ))) - } else { - Some(Cow::Owned(format!("Build({})", activity_build.drv_path))) - } - } - Activity::OptimizeStore(activity_optimize_store) => { - warn!("Unimplemented display_name OptimizeStore"); - // TODO - Some(Cow::Borrowed("OptimizeStore")) - } - Activity::VerifyPaths(activity_verify_paths) => { - warn!("Unimplemented display_name VerifyPaths"); - // TODO - Some(Cow::Borrowed("VerifyPaths")) - } - Activity::Substitute(activity_substitute) => { - warn!("Unimplemented display_name Substitute"); - // TODO - Some(Cow::Borrowed("Substitute")) - } - Activity::QueryPathInfo(activity_query_path_info) => { - warn!("Unimplemented display_name QueryPathInfo"); - // TODO - Some(Cow::Borrowed("QueryPathInfo")) - } - Activity::PostBuildHook(activity_post_build_hook) => { - warn!("Unimplemented display_name PostBuildHook"); - // TODO - Some(Cow::Borrowed("PostBuildHook")) - } - Activity::BuildWaiting(activity_build_waiting) => { - warn!("Unimplemented display_name BuildWaiting"); - // TODO - Some(Cow::Borrowed("BuildWaiting")) - } - Activity::FetchTree(activity_fetch_tree) => { - warn!("Unimplemented display_name FetchTree"); - // TODO - Some(Cow::Borrowed("FetchTree")) - } - } - } -} - -struct ActivityUnknown { - id: u64, - parent: u64, - state: ActivityState, -} -struct ActivityCopyPath { - id: u64, - parent: u64, - state: ActivityState, -} -struct ActivityFileTransfer { - id: u64, - parent: u64, - state: ActivityState, - url: String, -} -struct ActivityRealize { - id: u64, - parent: u64, - state: ActivityState, -} -struct ActivityCopyPaths { - id: u64, - parent: u64, - state: ActivityState, -} -struct ActivityBuilds { - id: u64, - parent: u64, - state: ActivityState, -} -struct ActivityBuild { - id: u64, - parent: u64, - state: ActivityState, - drv_path: String, - machine_name: Option, -} -struct ActivityOptimizeStore { - id: u64, - parent: u64, - state: ActivityState, -} -struct ActivityVerifyPaths { - id: u64, - parent: u64, - state: ActivityState, -} -struct ActivitySubstitute { - id: u64, - parent: u64, - state: ActivityState, -} -struct ActivityQueryPathInfo { - id: u64, - parent: u64, - state: ActivityState, -} -struct ActivityPostBuildHook { - id: u64, - parent: u64, - state: ActivityState, -} -struct ActivityBuildWaiting { - id: u64, - state: ActivityState, -} -struct ActivityFetchTree { - id: u64, - parent: u64, - state: ActivityState, -} - -trait ActivityTreeEntry { - fn get_activity_id(&self) -> u64; - - fn get_parent_id(&self) -> u64; -} - -impl ActivityTreeEntry for Activity { - fn get_activity_id(&self) -> u64 { - match self { - Activity::Unknown(activity_unknown) => activity_unknown.get_activity_id(), - Activity::CopyPath(activity_copy_path) => activity_copy_path.get_activity_id(), - Activity::FileTransfer(activity_file_transfer) => { - activity_file_transfer.get_activity_id() - } - Activity::Realize(activity_realize) => activity_realize.get_activity_id(), - Activity::CopyPaths(activity_copy_paths) => activity_copy_paths.get_activity_id(), - Activity::Builds(activity_builds) => activity_builds.get_activity_id(), - Activity::Build(activity_build) => activity_build.get_activity_id(), - Activity::OptimizeStore(activity_optimize_store) => { - activity_optimize_store.get_activity_id() - } - Activity::VerifyPaths(activity_verify_paths) => activity_verify_paths.get_activity_id(), - Activity::Substitute(activity_substitute) => activity_substitute.get_activity_id(), - Activity::QueryPathInfo(activity_query_path_info) => { - activity_query_path_info.get_activity_id() - } - Activity::PostBuildHook(activity_post_build_hook) => { - activity_post_build_hook.get_activity_id() - } - Activity::BuildWaiting(activity_build_waiting) => { - activity_build_waiting.get_activity_id() - } - Activity::FetchTree(activity_fetch_tree) => activity_fetch_tree.get_activity_id(), - } - } - - fn get_parent_id(&self) -> u64 { - match self { - Activity::Unknown(activity_unknown) => activity_unknown.get_parent_id(), - Activity::CopyPath(activity_copy_path) => activity_copy_path.get_parent_id(), - Activity::FileTransfer(activity_file_transfer) => { - activity_file_transfer.get_parent_id() - } - Activity::Realize(activity_realize) => activity_realize.get_parent_id(), - Activity::CopyPaths(activity_copy_paths) => activity_copy_paths.get_parent_id(), - Activity::Builds(activity_builds) => activity_builds.get_parent_id(), - Activity::Build(activity_build) => activity_build.get_parent_id(), - Activity::OptimizeStore(activity_optimize_store) => { - activity_optimize_store.get_parent_id() - } - Activity::VerifyPaths(activity_verify_paths) => activity_verify_paths.get_parent_id(), - Activity::Substitute(activity_substitute) => activity_substitute.get_parent_id(), - Activity::QueryPathInfo(activity_query_path_info) => { - activity_query_path_info.get_parent_id() - } - Activity::PostBuildHook(activity_post_build_hook) => { - activity_post_build_hook.get_parent_id() - } - Activity::BuildWaiting(activity_build_waiting) => { - activity_build_waiting.get_parent_id() - } - Activity::FetchTree(activity_fetch_tree) => activity_fetch_tree.get_parent_id(), - } - } -} - -impl ActivityTreeEntry for ActivityUnknown { - fn get_activity_id(&self) -> u64 { - self.id - } - - fn get_parent_id(&self) -> u64 { - self.parent - } -} -impl ActivityTreeEntry for ActivityCopyPath { - fn get_activity_id(&self) -> u64 { - self.id - } - - fn get_parent_id(&self) -> u64 { - self.parent - } -} -impl ActivityTreeEntry for ActivityFileTransfer { - fn get_activity_id(&self) -> u64 { - self.id - } - - fn get_parent_id(&self) -> u64 { - self.parent - } -} -impl ActivityTreeEntry for ActivityRealize { - fn get_activity_id(&self) -> u64 { - self.id - } - - fn get_parent_id(&self) -> u64 { - self.parent - } -} -impl ActivityTreeEntry for ActivityCopyPaths { - fn get_activity_id(&self) -> u64 { - self.id - } - - fn get_parent_id(&self) -> u64 { - self.parent - } -} -impl ActivityTreeEntry for ActivityBuilds { - fn get_activity_id(&self) -> u64 { - self.id - } - - fn get_parent_id(&self) -> u64 { - self.parent - } -} -impl ActivityTreeEntry for ActivityBuild { - fn get_activity_id(&self) -> u64 { - self.id - } - - fn get_parent_id(&self) -> u64 { - self.parent - } -} -impl ActivityTreeEntry for ActivityOptimizeStore { - fn get_activity_id(&self) -> u64 { - self.id - } - - fn get_parent_id(&self) -> u64 { - self.parent - } -} -impl ActivityTreeEntry for ActivityVerifyPaths { - fn get_activity_id(&self) -> u64 { - self.id - } - - fn get_parent_id(&self) -> u64 { - self.parent - } -} -impl ActivityTreeEntry for ActivitySubstitute { - fn get_activity_id(&self) -> u64 { - self.id - } - - fn get_parent_id(&self) -> u64 { - self.parent - } -} -impl ActivityTreeEntry for ActivityQueryPathInfo { - fn get_activity_id(&self) -> u64 { - self.id - } - - fn get_parent_id(&self) -> u64 { - self.parent - } -} -impl ActivityTreeEntry for ActivityPostBuildHook { - fn get_activity_id(&self) -> u64 { - self.id - } - - fn get_parent_id(&self) -> u64 { - self.parent - } -} -impl ActivityTreeEntry for ActivityBuildWaiting { - fn get_activity_id(&self) -> u64 { - self.id - } - - fn get_parent_id(&self) -> u64 { - // Parent seems to always be zero? - 0 - } -} -impl ActivityTreeEntry for ActivityFetchTree { - fn get_activity_id(&self) -> u64 { - self.id - } - - fn get_parent_id(&self) -> u64 { - self.parent - } -} diff --git a/src/nix_util/transparent_iter.rs b/src/nix_util/transparent_iter.rs new file mode 100644 index 0000000..f52ab62 --- /dev/null +++ b/src/nix_util/transparent_iter.rs @@ -0,0 +1,90 @@ +use super::activity_tree::ActivityTree; +use super::activity_tree::ActivityTreeEntry; + +/// An iterator that returns either the original activity if it is not transparent, or if it is transparent, the earliest non-transparent children. +pub(crate) struct TransparentIter<'tree> { + activity_tree: &'tree ActivityTree, + origin: Option<&'tree ActivityTreeEntry>, + child_index: Vec, +} + +impl<'tree> Iterator for TransparentIter<'tree> { + type Item = &'tree ActivityTreeEntry; + + fn next(&mut self) -> Option { + let origin = match self.origin { + Some(o) => o, + None => { + return None; + } + }; + + if !origin.get_activity().is_transparent() { + let origin = self + .origin + .take() + .expect("We would have returned early if origin was None."); + return Some(origin); + } + + if self.child_index.is_empty() { + self.child_index.push(0); + } + + loop { + let current_entry = self.get_current_entry(); + if let Some(child) = current_entry { + if child.get_activity().is_transparent() { + self.child_index.push(0); + } else { + let last_index = self.child_index.last_mut().expect("Stack cannot be empty."); + *last_index += 1; + return Some(child); + } + } else { + self.child_index.pop(); + if self.child_index.is_empty() { + self.origin.take(); + return None; + } + let last_index = self.child_index.last_mut().expect("Stack cannot be empty."); + *last_index += 1; + } + } + } +} + +impl<'tree> TransparentIter<'tree> { + pub(crate) fn new( + activity_tree: &'tree ActivityTree, + origin: &'tree ActivityTreeEntry, + ) -> TransparentIter<'tree> { + TransparentIter { + activity_tree, + origin: Some(origin), + child_index: Vec::new(), + } + } + + fn get_current_entry(&mut self) -> Option<&'tree ActivityTreeEntry> { + let mut current_entry = match self.origin { + Some(o) => o, + None => { + return None; + } + }; + for ind in self.child_index.iter() { + if let Some(child) = current_entry + .get_child_ids() + .get(*ind) + .map(|child_index| self.activity_tree.get(child_index)) + { + current_entry = child; + continue; + } else { + return None; + } + } + Some(current_entry) + } +}