From 09ff01315c2e17c767c62e8b100d1fdc5e035132 Mon Sep 17 00:00:00 2001 From: Robin Candau Date: Sun, 8 Nov 2026 18:43:30 +0100 Subject: [PATCH] feat: Add genact support for compilation animations If [genact](https://github.com/svenstaro/genact) is installed, use it to display compilation animations. If it's not installed, show a simple "spinner" animation instead. Also drop the real time countdown (which doesn't serve much purpose once the CI/CD perk is unlocked and doesn't play nice with `genact` animations), and add minors visual improvements & slight refactoring. --- Cargo.lock | 10 ++++++ Cargo.toml | 1 + README.md | 4 +++ src/command.rs | 14 ++++----- src/compile.rs | 79 +++++++++++++++++++++++++++++++++-------------- src/parameters.rs | 6 ++-- 6 files changed, 81 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 892663e..459c89e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -334,6 +334,7 @@ dependencies = [ "rustyline", "serde", "serde_json", + "which", ] [[package]] @@ -703,6 +704,15 @@ dependencies = [ "libc", ] +[[package]] +name = "which" +version = "8.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae2f2b2b816647a1cab1acc91f5bd20812d53cb344382635ec2181940c8034f" +dependencies = [ + "libc", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 053645e..b460459 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ rand = "0.10.3" rustyline = "19.0.0" serde = { version = "1.0.229", features = ["derive"] } serde_json = "1.0.154" +which = "8.0.6" [dev-dependencies] assert_cmd = "2.2.2" diff --git a/README.md b/README.md index 812379d..99461bf 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,10 @@ Then type 'help' to see the list of available commands and 'guide' to see the ga ## Installation +## Runtime dependencies + +- [`genact`](https://github.com/svenstaro/genact) (optional) - For compilation animations + ### Packages [![Packaging status](https://repology.org/badge/vertical-allrepos/incrust.svg)](https://repology.org/project/incrust/versions) diff --git a/src/command.rs b/src/command.rs index e20d8f8..34219c2 100644 --- a/src/command.rs +++ b/src/command.rs @@ -19,7 +19,7 @@ pub fn compile(game_state: &mut GameState, interrupt_rx: &Receiver<()>) -> anyho // Start the fake compile process and loop over it // Break the loop if it gets interrupted or if the CI/CD perk is not enabled loop { - let interrupted = compile::compile_proc(game_state, interrupt_rx)?; + let interrupted = compile::compile_process(game_state, interrupt_rx)?; if interrupted || !game_state.ci_cd_enabled { break; @@ -46,15 +46,15 @@ pub fn manage(game_state: &mut GameState, submenu_editor: &mut PromptEditor) -> pub fn status(game_state: &GameState) { println!("[RESOURCES]"); println!(); - println!("CPU {} CORE(S)", game_state.cpu); - println!("RAM {} GB", game_state.ram); - println!("DISK {} GB", game_state.disk); - println!("CREDITS {:.2}", game_state.credits); + println!("CPU {} CORE(S)", game_state.cpu); + println!("RAM {} GB", game_state.ram); + println!("DISK {} GB", game_state.disk); + println!("CREDITS {:.2}", game_state.credits); println!(); println!("[PERKS]"); println!(); println!( - "CI/CD {}", + "CI/CD {}", if !game_state.ci_cd_unlocked { "LOCKED" } else if game_state.ci_cd_enabled { @@ -64,7 +64,7 @@ pub fn status(game_state: &GameState) { } ); println!( - "Auto-Scaling {}", + "Auto-Scaling {}", if !game_state.auto_scaling_unlocked { "LOCKED" } else if game_state.auto_scaling_enabled { diff --git a/src/compile.rs b/src/compile.rs index f012228..0bee559 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -4,22 +4,20 @@ use anyhow::Context; use colored::Colorize; use rand::random; use std::io::{self, Write}; +use std::process::{Child, Command, Stdio}; use std::sync::mpsc::{Receiver, TryRecvError}; use std::thread; use std::time::{Duration, Instant}; +use which::which; use crate::parameters; use crate::statefile::{self, GameState}; use crate::upgrade; -pub fn compile_proc( +pub fn compile_process( game_state: &mut GameState, interrupt_rx: &Receiver<()>, ) -> anyhow::Result { - // Set timestamps to calculate compilation remaining time - let start = Instant::now(); - let mut last_remaining = u64::MAX; - // Silly messages println!( "{} Synchronizing imaginary dependencies...", @@ -46,55 +44,68 @@ pub fn compile_proc( parameters::show_compilation_parameters(game_state); println!(); - // Convert compile time to a Duration (used later to calculate remaining time) + // Convert compile time to a Duration (used later to determine when the compilation is over) let compile_time = Duration::from_secs_f64(compile_time); + // Start compilation timer + let start = Instant::now(); + + // Start genact (if it's installed, directly checked by the start_genact() function) + let mut genact_process = start_genact(); + + // Fallback compilation animation if genact is not installed + let spinner = ["-", "\\", "|", "/"]; + let mut spinner_index = 0; + // Main compilation loop loop { // Check for interruption request via Ctrl+C match interrupt_rx.try_recv() { Ok(()) => { + // Stop genact (if needed) + stop_genact(&mut genact_process); + println!(); println!( - "\n{} Compilation interrupted, no credits earned...", + "{} Compilation interrupted, no credits earned...", "[INFO]".blue() ); return Ok(true); } Err(TryRecvError::Empty) => {} Err(TryRecvError::Disconnected) => { - eprintln!("\nWarning: Interruption handler disconnected."); + // Stop genact (if needed) + stop_genact(&mut genact_process); + eprintln!("Warning: Interruption handler disconnected."); return Ok(true); } } - // Calculate remaining time - let remaining = compile_time.saturating_sub(start.elapsed()); - // Check whether compilation is complete - if remaining.is_zero() { + if start.elapsed() >= compile_time { break; } - // Display countdown - let remaining_secs = remaining.as_secs_f64().ceil() as u64; - - if remaining_secs != last_remaining { - let minutes = remaining_secs / 60; - let seconds = remaining_secs % 60; - - print!("\rCompiling... {minutes:02}:{seconds:02} remaining "); + // Display fallback animation if genact isn't running / installed + if genact_process.is_none() { + print!("\rCompiling {}", spinner[spinner_index]); let _ = io::stdout().flush(); - last_remaining = remaining_secs; + spinner_index = (spinner_index + 1) % spinner.len(); } - // Wait before checking again + // Wait before checking restarting the loop thread::sleep(Duration::from_millis(100)); } + // Stop genact (if needed) + stop_genact(&mut genact_process); + + // Move to a new line after the animation + println!(); + // Compilation completion message - println!("\rCompilation completed! "); + println!("Compilation completed!"); println!(); // Roll for compilation failure @@ -125,3 +136,25 @@ pub fn compile_proc( Ok(false) } + +// Helper to start genact for compilation animation (if it's installed) +fn start_genact() -> Option { + // Check if genact is installed + let genact_path = which("genact").ok()?; + + // Run genact + Command::new(genact_path) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .ok() +} + +// Helper to stop genact (if there is a genact process running) +fn stop_genact(genact_process: &mut Option) { + // Kill the running genact process spawned by incrust (if there's one) + if let Some(mut child) = genact_process.take() { + let _ = child.kill(); + let _ = child.wait(); + } +} diff --git a/src/parameters.rs b/src/parameters.rs index 44e4561..2e9201a 100644 --- a/src/parameters.rs +++ b/src/parameters.rs @@ -59,12 +59,12 @@ pub fn show_compilation_parameters(game_state: &GameState) -> (f64, f64, f64) { // Show values println!( - "Compile time {:02}:{:02}", + "Compile time {:02}:{:02}", (compile_time / 60.0) as u64, compile_time as u64 % 60 ); - println!("Credit reward {credit_reward:.2}"); - println!("Failure {:.1}%", failure_probability * 100.0); + println!("Credit reward {credit_reward:.2}"); + println!("Failure probability {:.1}%", failure_probability * 100.0); // Return values (compile_time, credit_reward, failure_probability)