rework state system

This commit is contained in:
2025-11-09 19:58:45 +00:00
parent b08a3c682e
commit eadb22404d
3 changed files with 258 additions and 190 deletions

View File

@@ -75,7 +75,7 @@ impl Display for BuildStepId {
// --- Action // --- Action
// --- // ---
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub enum StartFields<'a> { pub enum StartFields<'a> {
Unknown, Unknown,
CopyPath { CopyPath {

View File

@@ -4,18 +4,13 @@ use std::time::Duration;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use crate::action::StartFields; 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;
pub mod action_raw; pub mod action_raw;
pub mod nix_path; pub mod nix_path;
pub mod state_manager; pub mod state_manager;
struct State {
progress: MultiProgress,
manager: StateManager,
}
fn main() -> Result<(), color_eyre::Report> { fn main() -> Result<(), color_eyre::Report> {
color_eyre::install().unwrap(); color_eyre::install().unwrap();
let lines = std::fs::read_to_string("build.log")?; let lines = std::fs::read_to_string("build.log")?;
@@ -52,7 +47,7 @@ fn main() -> Result<(), color_eyre::Report> {
for line in lines.lines() { for line in lines.lines() {
lines_pb.inc(1); lines_pb.inc(1);
let line = line.strip_prefix("@nix ").unwrap_or(line); 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)?; let action = action::Action::parse(line)?;
match action { match action {
action::Action::Msg { level, msg } => { action::Action::Msg { level, msg } => {
@@ -67,116 +62,136 @@ fn main() -> Result<(), color_eyre::Report> {
parent, parent,
text: _, text: _,
} => { } => {
state.progress.println(format!("START {start_type:?}")); // state.progress.println(format!("START {start_type:?}"));
match start_type { if let Some(parent) = state.manager.get(*parent) {
StartFields::Substitute { source, target } => { let mut child = parent.clone();
state child.merge(&mut state, start_type);
.manager child.tick(&mut state);
.insert_parent(*id, BuildState::new(Some(source.to_string()), None)); state.manager.insert_parent(*id, child);
};
let build_state = state.manager.get_mut(*id).unwrap(); // match start_type {
build_state.state = BuildEnumState::Substituting; // StartFields::Substitute { source, target } => {
}
StartFields::CopyPath {
path,
origin,
destination,
} => {
state.manager.add_child(*id, *parent);
}
// StartFields::QueryPathInfo { path, source: _ } => {
// state // state
// .manager // .manager
// .insert_parent(*id, BuildState::new(Some(path.to_string()), None)); // .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::FileTransfer { target } => { // StartFields::CopyPath {
// state.progress.println(format!( // path,
// "START FileTransfer (id: {}, parent: {}): target={}", // origin,
// id, parent, target // destination,
// ))?; // } => {
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); // state.manager.add_child(*id, *parent);
// Add child ID mapping to 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 } => { action::Action::Stop { id } => {
state.manager.remove(*id);
// Stop will only return Some when the last reference is stopped // Stop will only return Some when the last reference is stopped
if let Some(build_state) = state.manager.stop(*id) { // if let Some(build_state) = state.manager.stop(*id) {
if let Some(pb) = &build_state.progress_bar { // if let Some(pb) = &build_state.progress_bar {
pb.finish_and_clear(); // pb.finish_and_clear();
state.progress.remove(pb); // state.progress.remove(pb);
} // }
state.progress.println(format!( // state.progress.println(format!(
"Completed: {}", // "Completed: {}",
build_state.path.as_deref().unwrap_or("unknown") // build_state.path.as_deref().unwrap_or("unknown")
))?; // ))?;
} // }
} }
action::Action::Result { id, fields } => match fields { action::Action::Result { id, fields } => match fields {
action::ResultFields::FetchStatus(status) => {
state.progress.println(format!(
"RESULT FetchStatus (id: {}): status={}",
id, status
))?;
}
action::ResultFields::Progress { action::ResultFields::Progress {
done, done,
expected, expected,
running, running,
failed, failed,
} => { } => {
if expected == 0 {
continue;
};
if let Some(child) = state.manager.get_mut(*id) {
child.progress(done, expected);
}
// sleep(Duration::from_millis(1)); // sleep(Duration::from_millis(1));
if let Some(build_state) = state.manager.get_mut(*id) // if let Some(build_state) = state.manager.get_mut(*id)
&& expected > 0 // && expected > 0
{ // {
let percentage = if expected == 0 { // let percentage = if expected == 0 {
0 // 0
} else { // } else {
(done * 100 / expected) as u64 // (done * 100 / expected) as u64
}; // };
match &build_state.progress_bar { // match &build_state.progress_bar {
Some(pb) => { // Some(pb) => {
if percentage > pb.position() { // if percentage > pb.position() {
pb.set_position(percentage); // pb.set_position(percentage);
}; // };
} // }
None => { // None => {
state.progress.println(format!( // state.progress.println(format!(
"Creating progress bar for id {} (done: {}, expected: {})", // "Creating progress bar for id {} (done: {}, expected: {})",
id, done, expected // id, done, expected
))?; // ))?;
let n = match build_state.state { // let n = match build_state.state {
BuildEnumState::Downloading => "Downloading", // BuildEnumState::Downloading => "Downloading",
BuildEnumState::Substituting => "Substituting", // BuildEnumState::Substituting => "Substituting",
_ => "Processing", // _ => "Processing",
}; // };
let name = // let name =
nix_path::extract_full_name(build_state.path.as_ref().unwrap()); // nix_path::extract_full_name(build_state.path.as_ref().unwrap());
let pb = state.progress.add( // let pb = state.progress.add(
ProgressBar::new(100) // ProgressBar::new(100)
.with_style( // .with_style(
ProgressStyle::default_bar() // ProgressStyle::default_bar()
.template("{msg} [{bar:40.cyan/blue}] {pos:>3}%") // .template("{msg} [{bar:40.cyan/blue}] {pos:>3}%")
.unwrap(), // .unwrap(),
) // )
.with_message(format!( // .with_message(format!(
"{n} {}", // "{n} {}",
name.as_deref().unwrap_or("unknown") // name.as_deref().unwrap_or("unknown")
)), // )),
); // );
pb.set_position(percentage); // pb.set_position(percentage);
build_state.progress_bar = Some(pb); // build_state.progress_bar = Some(pb);
} // }
}; // };
} else { // } else {
state.progress.println(format!( // // state.progress.println(format!(
"RESULT Progress (id: {}): done={}, expected={}, running={}, failed={}", // // "RESULT Progress (id: {}): done={}, expected={}, running={}, failed={}",
id, done, expected, running, failed // // id, done, expected, running, failed
))?; // // ))?;
}; // };
} }
_ => {} _ => {}
}, },

View File

@@ -1,107 +1,160 @@
use indicatif::ProgressBar; use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use std::collections::HashMap; use std::{borrow::Cow, collections::HashMap};
#[derive(Debug, Clone, Copy, PartialEq, Eq)] use crate::action::StartFields;
pub enum BuildEnumState {
Query, pub struct State<'a> {
Downloading, pub progress: MultiProgress,
Substituting, 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<String>, pub path: Option<String>,
pub progress_bar: Option<ProgressBar>, pub progress_bar: Option<ProgressBar>,
pub state: BuildEnumState, pub state: BuildEnumState<'a>,
ref_count: usize, ref_count: usize,
} }
impl<'a> BuildState<'a> {
impl BuildState { pub fn new() -> Self {
pub fn new(path: Option<String>, progress_bar: Option<ProgressBar>) -> Self {
Self { Self {
path, path: None,
progress_bar, progress_bar: None,
state: BuildEnumState::Query, state: BuildEnumState::Init,
ref_count: 1, ref_count: 1,
} }
} }
} }
pub struct StateManager { impl<'a> BuildState<'a> {
states: HashMap<u64, BuildState>, pub fn merge(&mut self, state: &mut State, start_type: StartFields<'a>) {
id_to_canonical: HashMap<u64, u64>, 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);
};
}
_ => {}
}
}
} }
impl StateManager { pub struct StateManager<'a> {
states: HashMap<u64, BuildState<'a>>,
}
impl<'a> StateManager<'a> {
pub fn new() -> Self { pub fn new() -> Self {
Self { let mut states = HashMap::new();
states: HashMap::new(), states.insert(0, BuildState::new());
id_to_canonical: HashMap::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>> {
pub fn insert_parent(&mut self, id: u64, state: BuildState) { self.states.get_mut(&id)
self.id_to_canonical.insert(id, id); // parent maps to itself }
pub fn insert_parent(&mut self, id: u64, state: BuildState<'a>) {
self.states.insert(id, state); self.states.insert(id, state);
} }
pub fn get_or_insert(&mut self, id: u64) -> &mut BuildState<'a> {
pub fn add_child(&mut self, child_id: u64, parent_id: u64) { self.states.entry(id).or_insert_with(BuildState::new)
if let Some(&canonical_id) = self.id_to_canonical.get(&parent_id) { }
self.id_to_canonical.insert(child_id, canonical_id); pub fn remove(&mut self, id: u64) {
if let Some(state) = self.states.get(&id) {
if let Some(state) = self.states.get_mut(&canonical_id) { if let Some(pb) = &state.progress_bar {
state.ref_count += 1; pb.finish_and_clear();
self.states.remove(&id);
} }
} }
} 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<BuildState> {
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<u64> {
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()
} }
} }