From 05d0a904029b50f37c76858aceaaeca21b998f17 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Wed, 20 Dec 2023 02:19:26 +0200 Subject: [PATCH] Show information about multiple farms in UI during normal operation (no configuration yet) --- src/frontend/running.rs | 173 ++++++++++++++++++++++------------- src/frontend/running/farm.rs | 139 ++++++++++++++++++++++++++++ src/main.rs | 5 +- 3 files changed, 253 insertions(+), 64 deletions(-) create mode 100644 src/frontend/running/farm.rs diff --git a/src/frontend/running.rs b/src/frontend/running.rs index 844ee461..10585bcf 100644 --- a/src/frontend/running.rs +++ b/src/frontend/running.rs @@ -1,7 +1,12 @@ +mod farm; + +use crate::backend::config::RawConfig; use crate::backend::farmer::{PlottingKind, PlottingState}; use crate::backend::node::{SyncKind, SyncState}; use crate::backend::{FarmerNotification, NodeNotification}; +use crate::frontend::running::farm::{FarmWidget, FarmWidgetInit, FarmWidgetInput}; use gtk::prelude::*; +use relm4::factory::FactoryHashMap; use relm4::prelude::*; use subspace_core_primitives::BlockNumber; use tracing::warn; @@ -15,6 +20,7 @@ pub enum RunningInput { Initialize { best_block_number: BlockNumber, initial_plotting_states: Vec, + raw_config: RawConfig, }, NodeNotification(NodeNotification), FarmerNotification(FarmerNotification), @@ -37,6 +43,7 @@ struct FarmerState { pub struct RunningView { node_state: NodeState, farmer_state: FarmerState, + farms: FactoryHashMap, } #[relm4::component(pub)] @@ -62,7 +69,6 @@ impl Component for RunningView { set_label: "Consensus node", }, - // TODO: Match only because `if let Some(x) = y` is not yet supported here: https://github.com/Relm4/Relm4/issues/582 #[transition = "SlideUpDown"] match model.node_state.sync_state { SyncState::Unknown => gtk::Box { @@ -79,6 +85,8 @@ impl Component for RunningView { set_spacing: 10, gtk::Box { + set_spacing: 5, + gtk::Label { set_halign: gtk::Align::Start, @@ -102,7 +110,6 @@ impl Component for RunningView { }, gtk::Spinner { - set_margin_start: 5, start: (), }, }, @@ -139,86 +146,83 @@ impl Component for RunningView { // TODO: Render all farms, not just the first one // TODO: Match only because `if let Some(x) = y` is not yet supported here: https://github.com/Relm4/Relm4/issues/582 #[transition = "SlideUpDown"] - match ( - model.farmer_state.piece_cache_sync_progress, - model.farmer_state.plotting_state.get(0).copied().unwrap_or_default(), - model.node_state.sync_state - ) { - (progress, _, _) if progress < 100.0 => gtk::Box { + if model.farmer_state.piece_cache_sync_progress < 100.0 { + gtk::Box { set_orientation: gtk::Orientation::Vertical, set_spacing: 10, gtk::Box { - gtk::Label { - set_halign: gtk::Align::Start, - - #[watch] - set_label: &format!("Piece cache sync {progress:.2}%"), - set_tooltip: "Plotting starts after piece cache sync is complete", - }, + set_spacing: 5, + set_tooltip: "Plotting starts after piece cache sync is complete", - gtk::Spinner { - set_margin_start: 5, - start: (), - }, - }, - - gtk::ProgressBar { - #[watch] - set_fraction: progress as f64 / 100.0, - }, - }, - (_, PlottingState::Plotting { kind, progress, speed }, SyncState::Idle) => gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_spacing: 10, - - gtk::Box { gtk::Label { set_halign: gtk::Align::Start, #[watch] - set_label: &{ - let kind = match kind { - PlottingKind::Initial => "Initial plotting, not farming", - PlottingKind::Replotting => "Replotting, farming", - }; - - format!( - "{} {:.2}%{}", - kind, - progress, - speed - .map(|speed| format!(", {:.2} sectors/h", 3600.0 / speed)) - .unwrap_or_default(), - ) - }, - set_tooltip: "Farming starts after initial plotting is complete", + set_label: &format!( + "Piece cache sync {:.2}%", + model.farmer_state.piece_cache_sync_progress + ), }, gtk::Spinner { - set_margin_start: 5, start: (), }, }, gtk::ProgressBar { #[watch] - set_fraction: progress as f64 / 100.0, + set_fraction: model.farmer_state.piece_cache_sync_progress as f64 / 100.0, }, - }, - (_, PlottingState::Idle, SyncState::Idle) => gtk::Box { - gtk::Label { - #[watch] - set_label: "Farming", - } - }, - _ => gtk::Box { - gtk::Label { - #[watch] - set_label: "Waiting for node to sync first", - } - }, + } + } else { + gtk::Label { + set_halign: gtk::Align::Start, + #[watch] + set_label: &{ + if matches!(model.node_state.sync_state, SyncState::Idle) { + let mut statuses = Vec::new(); + let plotting = model.farmer_state.plotting_state.iter().any(|plotting_state| { + matches!(plotting_state, PlottingState::Plotting { kind: PlottingKind::Initial, .. }) + }); + let replotting = model.farmer_state.plotting_state.iter().any(|plotting_state| { + matches!(plotting_state, PlottingState::Plotting { kind: PlottingKind::Replotting, .. }) + }); + let idle = model.farmer_state.plotting_state.iter().any(|plotting_state| { + matches!(plotting_state, PlottingState::Idle) + }); + if plotting { + statuses.push(if statuses.is_empty() { + "Plotting" + } else { + "plotting" + }); + } + if matches!(model.node_state.sync_state, SyncState::Idle) && (replotting || idle) { + statuses.push(if statuses.is_empty() { + "Farming" + } else { + "farming" + }); + } + if replotting { + statuses.push(if statuses.is_empty() { + "Replotting" + } else { + "replotting" + }); + } + + statuses.join(", ") + } else { + "Waiting for node to sync first".to_string() + } + }, + // TODO: Show summarized state of all farms: Plotting, Replotting, Farming + } }, + + model.farms.widget(), }, } } @@ -228,9 +232,21 @@ impl Component for RunningView { _root: &Self::Root, _sender: ComponentSender, ) -> ComponentParts { + let farms = FactoryHashMap::builder() + .launch( + gtk::Box::builder() + .margin_start(10) + .margin_end(10) + .orientation(gtk::Orientation::Vertical) + .spacing(10) + .build(), + ) + .detach(); + let model = Self { node_state: NodeState::default(), farmer_state: FarmerState::default(), + farms, }; let widgets = view_output!(); @@ -249,7 +265,23 @@ impl RunningView { RunningInput::Initialize { best_block_number, initial_plotting_states, + raw_config, } => { + for (farm_index, (initial_plotting_state, farm)) in initial_plotting_states + .iter() + .copied() + .zip(raw_config.farms().iter().cloned()) + .enumerate() + { + self.farms.insert( + farm_index, + FarmWidgetInit { + initial_plotting_state, + farm, + }, + ); + } + self.node_state = NodeState { best_block_number, sync_state: SyncState::default(), @@ -282,6 +314,13 @@ impl RunningView { } } } + + let old_synced = matches!(self.node_state.sync_state, SyncState::Idle); + let new_synced = matches!(sync_state, SyncState::Idle); + if old_synced != new_synced { + self.farms + .broadcast(FarmWidgetInput::NodeSynced(new_synced)); + } self.node_state.sync_state = sync_state; } NodeNotification::BlockImported { number } => { @@ -295,6 +334,9 @@ impl RunningView { }, RunningInput::FarmerNotification(farmer_notification) => match farmer_notification { FarmerNotification::PlottingStateUpdate { farm_index, state } => { + self.farms + .send(&farm_index, FarmWidgetInput::PlottingStateUpdate(state)); + if let Some(plotting_state) = self.farmer_state.plotting_state.get_mut(farm_index) { @@ -304,6 +346,13 @@ impl RunningView { } } FarmerNotification::PieceCacheSyncProgress { progress } => { + let old_synced = self.farmer_state.piece_cache_sync_progress == 100.0; + let new_synced = progress == 100.0; + if old_synced != new_synced { + self.farms + .broadcast(FarmWidgetInput::PieceCacheSynced(new_synced)); + } + self.farmer_state.piece_cache_sync_progress = progress; } }, diff --git a/src/frontend/running/farm.rs b/src/frontend/running/farm.rs new file mode 100644 index 00000000..8416a500 --- /dev/null +++ b/src/frontend/running/farm.rs @@ -0,0 +1,139 @@ +use crate::backend::config::Farm; +use crate::backend::farmer::{PlottingKind, PlottingState}; +use gtk::prelude::*; +use relm4::prelude::*; +use std::path::PathBuf; + +#[derive(Debug)] +pub(super) struct FarmWidgetInit { + pub(super) initial_plotting_state: PlottingState, + pub(super) farm: Farm, +} + +#[derive(Debug, Copy, Clone)] +pub(super) enum FarmWidgetInput { + PlottingStateUpdate(PlottingState), + PieceCacheSynced(bool), + NodeSynced(bool), +} + +#[derive(Debug, Default)] +pub(super) struct FarmWidget { + path: PathBuf, + size: String, + plotting_state: PlottingState, + is_piece_cache_synced: bool, + is_node_synced: bool, +} + +#[relm4::factory(pub(super))] +impl FactoryComponent for FarmWidget { + type Init = FarmWidgetInit; + type Input = FarmWidgetInput; + type Output = (); + type CommandOutput = (); + type ParentWidget = gtk::Box; + type Index = usize; + + view! { + #[root] + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 10, + + gtk::Label { + set_halign: gtk::Align::Start, + set_label: &format!("{} [{}]:", self.path.display(), self.size), + }, + + #[transition = "SlideUpDown"] + match (self.plotting_state, self.is_piece_cache_synced, self.is_node_synced) { + (_, false, _) => gtk::Label { + set_halign: gtk::Align::Start, + set_label: "Waiting for piece cache sync", + }, + (PlottingState::Plotting { kind, progress, speed }, _, true) => gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 10, + + gtk::Box { + set_spacing: 5, + set_tooltip: "Farming starts after initial plotting is complete", + + gtk::Label { + set_halign: gtk::Align::Start, + + #[watch] + set_label: &{ + let kind = match kind { + PlottingKind::Initial => "Initial plotting, not farming", + PlottingKind::Replotting => "Replotting, farming", + }; + + format!( + "{} {:.2}%{}", + kind, + progress, + speed + .map(|speed| format!(", {:.2} sectors/h", 3600.0 / speed)) + .unwrap_or_default(), + ) + }, + }, + + gtk::Spinner { + start: (), + }, + }, + + gtk::ProgressBar { + #[watch] + set_fraction: progress as f64 / 100.0, + }, + }, + (PlottingState::Idle, _, true) => gtk::Box { + gtk::Label { + #[watch] + set_label: "Farming", + } + }, + _ => gtk::Box { + gtk::Label { + #[watch] + set_label: "Waiting for node to sync first", + } + }, + }, + }, + } + + fn init_model(init: Self::Init, _index: &Self::Index, _sender: FactorySender) -> Self { + Self { + path: init.farm.path, + size: init.farm.size, + plotting_state: init.initial_plotting_state, + is_piece_cache_synced: false, + is_node_synced: false, + } + } + + fn update(&mut self, input: Self::Input, _sender: FactorySender) { + self.process_input(input); + } +} + +impl FarmWidget { + fn process_input(&mut self, input: FarmWidgetInput) { + match input { + FarmWidgetInput::PlottingStateUpdate(state) => { + self.plotting_state = state; + } + FarmWidgetInput::PieceCacheSynced(synced) => { + self.is_piece_cache_synced = synced; + } + FarmWidgetInput::NodeSynced(synced) => { + self.is_node_synced = synced; + } + } + } +} diff --git a/src/main.rs b/src/main.rs index caf30b22..e5df1431 100644 --- a/src/main.rs +++ b/src/main.rs @@ -258,7 +258,7 @@ impl AsyncComponent for App { #[watch] set_visible: !model.status_bar_notification.is_none(), - #[name = "status_bar_notification_label"] + #[name(status_bar_notification_label)] gtk::Label { #[track = "!status_bar_notification_label.has_css_class(model.status_bar_notification.css_class())"] add_css_class: { @@ -478,11 +478,12 @@ impl App { best_block_number, initial_plotting_states, } => { - self.current_raw_config.replace(raw_config); + self.current_raw_config.replace(raw_config.clone()); self.current_view = View::Running; self.running_view.emit(RunningInput::Initialize { best_block_number, initial_plotting_states, + raw_config, }); } BackendNotification::Node(node_notification) => {