From eadb22404df6e366c5862da795f0c2e84af530b7 Mon Sep 17 00:00:00 2001 From: Nikkuss Date: Sun, 9 Nov 2025 19:58:45 +0000 Subject: [PATCH] rework state system --- src/action.rs | 2 +- src/main.rs | 223 +++++++++++++++++++++++-------------------- src/state_manager.rs | 223 ++++++++++++++++++++++++++----------------- 3 files changed, 258 insertions(+), 190 deletions(-) diff --git a/src/action.rs b/src/action.rs index 2d50b31..19f48ca 100644 --- a/src/action.rs +++ b/src/action.rs @@ -75,7 +75,7 @@ impl Display for BuildStepId { // --- Action // --- -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub enum StartFields<'a> { Unknown, CopyPath { diff --git a/src/main.rs b/src/main.rs index 1cde0b1..9ebcd71 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,18 +4,13 @@ use std::time::Duration; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use crate::action::StartFields; -use crate::state_manager::{BuildEnumState, BuildState, StateManager}; +use crate::state_manager::{BuildEnumState, BuildState, State, StateManager}; pub mod action; pub mod action_raw; pub mod nix_path; pub mod state_manager; -struct State { - progress: MultiProgress, - manager: StateManager, -} - fn main() -> Result<(), color_eyre::Report> { color_eyre::install().unwrap(); let lines = std::fs::read_to_string("build.log")?; @@ -52,7 +47,7 @@ fn main() -> Result<(), color_eyre::Report> { for line in lines.lines() { lines_pb.inc(1); let line = line.strip_prefix("@nix ").unwrap_or(line); - sleep(Duration::from_millis(10)); + sleep(Duration::from_millis(1)); let action = action::Action::parse(line)?; match action { action::Action::Msg { level, msg } => { @@ -67,116 +62,136 @@ fn main() -> Result<(), color_eyre::Report> { parent, text: _, } => { - state.progress.println(format!("START {start_type:?}")); - match start_type { - StartFields::Substitute { source, target } => { - state - .manager - .insert_parent(*id, BuildState::new(Some(source.to_string()), None)); - - let build_state = state.manager.get_mut(*id).unwrap(); - build_state.state = BuildEnumState::Substituting; - } - StartFields::CopyPath { - path, - origin, - destination, - } => { - state.manager.add_child(*id, *parent); - } - // StartFields::QueryPathInfo { path, source: _ } => { - // state - // .manager - // .insert_parent(*id, BuildState::new(Some(path.to_string()), None)); - // } - StartFields::FileTransfer { target } => { - // state.progress.println(format!( - // "START FileTransfer (id: {}, parent: {}): target={}", - // id, parent, target - // ))?; - if let Some(parent) = state.manager.get(*parent) { - state - .manager - .insert_parent(*id, BuildState::new(parent.path.clone(), None)); - let build_state = state.manager.get_mut(*id).unwrap(); - build_state.state = BuildEnumState::Downloading; - }; - - // state.manager.add_child(*id, *parent); - // Add child ID mapping to parent - } - _ => {} + // state.progress.println(format!("START {start_type:?}")); + if let Some(parent) = state.manager.get(*parent) { + let mut child = parent.clone(); + child.merge(&mut state, start_type); + child.tick(&mut state); + state.manager.insert_parent(*id, child); }; + // match start_type { + // StartFields::Substitute { source, target } => { + // state + // .manager + // .insert_parent(*id, BuildState::new(Some(source.to_string()), None)); + // + // let build_state = state.manager.get_mut(*id).unwrap(); + // build_state.state = BuildEnumState::Substituting; + // } + // StartFields::CopyPath { + // path, + // origin, + // destination, + // } => { + // state.manager.add_child(*id, *parent); + // } + // StartFields::QueryPathInfo { path, source } => { + // state.progress.println(format!( + // "START QueryPathInfo (id: {}, parent: {}): path={}", + // id, parent, path + // ))?; + // } + // StartFields::FileTransfer { target } => { + // // state.progress.println(format!( + // // "START FileTransfer (id: {}, parent: {}): target={}", + // // id, parent, target + // // ))?; + // if let Some(parent) = state.manager.get(*parent) { + // state + // .manager + // .insert_parent(*id, BuildState::new(parent.path.clone(), None)); + // let build_state = state.manager.get_mut(*id).unwrap(); + // build_state.state = BuildEnumState::Downloading; + // }; + // + // // state.manager.add_child(*id, *parent); + // // Add child ID mapping to parent + // } + // _ => {} + // }; } action::Action::Stop { id } => { + state.manager.remove(*id); // Stop will only return Some when the last reference is stopped - if let Some(build_state) = state.manager.stop(*id) { - if let Some(pb) = &build_state.progress_bar { - pb.finish_and_clear(); - state.progress.remove(pb); - } - state.progress.println(format!( - "Completed: {}", - build_state.path.as_deref().unwrap_or("unknown") - ))?; - } + // if let Some(build_state) = state.manager.stop(*id) { + // if let Some(pb) = &build_state.progress_bar { + // pb.finish_and_clear(); + // state.progress.remove(pb); + // } + // state.progress.println(format!( + // "Completed: {}", + // build_state.path.as_deref().unwrap_or("unknown") + // ))?; + // } } action::Action::Result { id, fields } => match fields { + action::ResultFields::FetchStatus(status) => { + state.progress.println(format!( + "RESULT FetchStatus (id: {}): status={}", + id, status + ))?; + } action::ResultFields::Progress { done, expected, running, failed, } => { - // sleep(Duration::from_millis(1)); - if let Some(build_state) = state.manager.get_mut(*id) - && expected > 0 - { - let percentage = if expected == 0 { - 0 - } else { - (done * 100 / expected) as u64 - }; - match &build_state.progress_bar { - Some(pb) => { - if percentage > pb.position() { - pb.set_position(percentage); - }; - } - None => { - state.progress.println(format!( - "Creating progress bar for id {} (done: {}, expected: {})", - id, done, expected - ))?; - let n = match build_state.state { - BuildEnumState::Downloading => "Downloading", - BuildEnumState::Substituting => "Substituting", - _ => "Processing", - }; - let name = - nix_path::extract_full_name(build_state.path.as_ref().unwrap()); - let pb = state.progress.add( - ProgressBar::new(100) - .with_style( - ProgressStyle::default_bar() - .template("{msg} [{bar:40.cyan/blue}] {pos:>3}%") - .unwrap(), - ) - .with_message(format!( - "{n} {}", - name.as_deref().unwrap_or("unknown") - )), - ); - pb.set_position(percentage); - build_state.progress_bar = Some(pb); - } - }; - } else { - state.progress.println(format!( - "RESULT Progress (id: {}): done={}, expected={}, running={}, failed={}", - id, done, expected, running, failed - ))?; + if expected == 0 { + continue; }; + if let Some(child) = state.manager.get_mut(*id) { + child.progress(done, expected); + } + // sleep(Duration::from_millis(1)); + // if let Some(build_state) = state.manager.get_mut(*id) + // && expected > 0 + // { + // let percentage = if expected == 0 { + // 0 + // } else { + // (done * 100 / expected) as u64 + // }; + // match &build_state.progress_bar { + // Some(pb) => { + // if percentage > pb.position() { + // pb.set_position(percentage); + // }; + // } + // None => { + // state.progress.println(format!( + // "Creating progress bar for id {} (done: {}, expected: {})", + // id, done, expected + // ))?; + // let n = match build_state.state { + // BuildEnumState::Downloading => "Downloading", + // BuildEnumState::Substituting => "Substituting", + // _ => "Processing", + // }; + // let name = + // nix_path::extract_full_name(build_state.path.as_ref().unwrap()); + // let pb = state.progress.add( + // ProgressBar::new(100) + // .with_style( + // ProgressStyle::default_bar() + // .template("{msg} [{bar:40.cyan/blue}] {pos:>3}%") + // .unwrap(), + // ) + // .with_message(format!( + // "{n} {}", + // name.as_deref().unwrap_or("unknown") + // )), + // ); + // pb.set_position(percentage); + // build_state.progress_bar = Some(pb); + // } + // }; + // } else { + // // state.progress.println(format!( + // // "RESULT Progress (id: {}): done={}, expected={}, running={}, failed={}", + // // id, done, expected, running, failed + // // ))?; + // }; } _ => {} }, diff --git a/src/state_manager.rs b/src/state_manager.rs index 133478b..8349cad 100644 --- a/src/state_manager.rs +++ b/src/state_manager.rs @@ -1,107 +1,160 @@ -use indicatif::ProgressBar; -use std::collections::HashMap; +use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; +use std::{borrow::Cow, collections::HashMap}; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum BuildEnumState { - Query, - Downloading, - Substituting, +use crate::action::StartFields; + +pub struct State<'a> { + pub progress: MultiProgress, + pub manager: StateManager<'a>, } -pub struct BuildState { +impl<'a> State<'a> { + pub fn add_pb(&mut self, pb: ProgressBar) -> ProgressBar { + self.progress.add(pb) + } +} + +#[derive(Debug, Clone)] +pub enum BuildEnumState<'a> { + Init, + Unknown, + Realise, + Builds, + CopyPaths, + Substitute { + source: Cow<'a, str>, + target: Cow<'a, str>, + }, + SubstituteCopy { + path: Cow<'a, str>, + }, + SubstituteFetch { + path: Cow<'a, str>, + bar: ProgressBar, + }, + Query { + path: Cow<'a, str>, + source: Cow<'a, str>, + }, + QueryFetch { + target: Cow<'a, str>, + }, +} +#[derive(Debug, Clone)] +pub struct BuildState<'a> { pub path: Option, pub progress_bar: Option, - pub state: BuildEnumState, + pub state: BuildEnumState<'a>, ref_count: usize, } - -impl BuildState { - pub fn new(path: Option, progress_bar: Option) -> Self { +impl<'a> BuildState<'a> { + pub fn new() -> Self { Self { - path, - progress_bar, - state: BuildEnumState::Query, + path: None, + progress_bar: None, + state: BuildEnumState::Init, ref_count: 1, } } } -pub struct StateManager { - states: HashMap, - id_to_canonical: HashMap, -} - -impl StateManager { - pub fn new() -> Self { - Self { - states: HashMap::new(), - id_to_canonical: HashMap::new(), +impl<'a> BuildState<'a> { + pub fn merge(&mut self, state: &mut State, start_type: StartFields<'a>) { + let state = match (&self.state, start_type.clone()) { + (BuildEnumState::Init, StartFields::QueryPathInfo { path, source }) => { + BuildEnumState::Query { path, source } + } + (BuildEnumState::Query { path, source }, StartFields::FileTransfer { target }) => { + BuildEnumState::QueryFetch { target } + } + (BuildEnumState::Init, StartFields::Realise) => BuildEnumState::Realise, + (BuildEnumState::Init, StartFields::Builds) => BuildEnumState::Builds, + (BuildEnumState::Init, StartFields::CopyPaths) => BuildEnumState::CopyPaths, + (BuildEnumState::Init, StartFields::Substitute { source, target }) => { + BuildEnumState::Substitute { source, target } + } + (BuildEnumState::Substitute { .. }, StartFields::CopyPath { path, .. }) => { + BuildEnumState::SubstituteCopy { path } + } + (BuildEnumState::SubstituteCopy { path }, StartFields::FileTransfer { .. }) => { + BuildEnumState::SubstituteFetch { + path: path.clone(), + bar: state.add_pb(ProgressBar::new(100)), + } + } + (_, StartFields::Unknown) => BuildEnumState::Unknown, + _ => unimplemented!( + "Unsupported state transition from {:?} with {:?}", + self.state, + start_type + ), + }; + self.state = state; + } + pub fn tick(&mut self, state: &mut State<'a>) { + match &self.state { + BuildEnumState::QueryFetch { target } => { + let pb = state.progress.add(ProgressBar::new_spinner()); + pb.set_message(format!("Fetching info for {}", target)); + pb.enable_steady_tick(std::time::Duration::from_millis(100)); + self.progress_bar = Some(pb); + } + BuildEnumState::SubstituteFetch { path, bar } => { + bar.set_message(format!("Fetching substitute for {}", path)); + bar.set_style( + ProgressStyle::default_bar() + .template("{msg} [{bar:40.cyan/blue}] {pos:>3}%") + .unwrap() + .progress_chars("=> "), + ); + // bar.enable_steady_tick(std::time::Duration::from_millis(100)); + // self.progress_bar = Some(bar.clone()); + } + _ => {} } } + pub fn progress(&mut self, done: u64, expected: u64) { + match &self.state { + BuildEnumState::SubstituteFetch { bar, .. } => { + let percentage = (done * 100 / expected) as u64; + if percentage > bar.position() { + bar.set_position(percentage); + }; + } + _ => {} + } + } +} - pub fn insert_parent(&mut self, id: u64, state: BuildState) { - self.id_to_canonical.insert(id, id); // parent maps to itself +pub struct StateManager<'a> { + states: HashMap>, +} + +impl<'a> StateManager<'a> { + pub fn new() -> Self { + let mut states = HashMap::new(); + states.insert(0, BuildState::new()); + Self { states } + } + pub fn get(&self, id: u64) -> Option<&BuildState<'a>> { + self.states.get(&id) + } + pub fn get_mut(&mut self, id: u64) -> Option<&mut BuildState<'a>> { + self.states.get_mut(&id) + } + pub fn insert_parent(&mut self, id: u64, state: BuildState<'a>) { self.states.insert(id, state); } - - pub fn add_child(&mut self, child_id: u64, parent_id: u64) { - if let Some(&canonical_id) = self.id_to_canonical.get(&parent_id) { - self.id_to_canonical.insert(child_id, canonical_id); - - if let Some(state) = self.states.get_mut(&canonical_id) { - state.ref_count += 1; + pub fn get_or_insert(&mut self, id: u64) -> &mut BuildState<'a> { + self.states.entry(id).or_insert_with(BuildState::new) + } + pub fn remove(&mut self, id: u64) { + if let Some(state) = self.states.get(&id) { + if let Some(pb) = &state.progress_bar { + pb.finish_and_clear(); + self.states.remove(&id); } } - } - - pub fn get(&self, id: u64) -> Option<&BuildState> { - self.id_to_canonical - .get(&id) - .and_then(|canonical_id| self.states.get(canonical_id)) - } - - pub fn get_mut(&mut self, id: u64) -> Option<&mut BuildState> { - self.id_to_canonical - .get(&id) - .copied() - .and_then(|canonical_id| self.states.get_mut(&canonical_id)) - } - - pub fn stop(&mut self, id: u64) -> Option { - let canonical_id = self.id_to_canonical.get(&id).copied()?; - - self.id_to_canonical.remove(&id); - - if let Some(state) = self.states.get_mut(&canonical_id) { - state.ref_count -= 1; - - if state.ref_count == 0 { - return self.states.remove(&canonical_id); - } - } - - None - } - - pub fn get_canonical_id(&self, id: u64) -> Option { - self.id_to_canonical.get(&id).copied() - } - - pub fn contains(&self, id: u64) -> bool { - self.id_to_canonical.contains_key(&id) - } - - pub fn len(&self) -> usize { - self.states.len() - } - - pub fn is_empty(&self) -> bool { - self.states.is_empty() - } -} - -impl Default for StateManager { - fn default() -> Self { - Self::new() + self.states.remove(&id); } }