diff options
-rw-r--r-- | CHANGELOG.md | 10 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | source/benoit/benoit.rs | 3 | ||||
-rw-r--r-- | source/benoit/benoit/configuration/load.rs | 52 | ||||
-rw-r--r-- | source/benoit/benoit/image/dump.rs | 6 | ||||
-rw-r--r-- | source/benoit/benoit/launcher.rs | 38 | ||||
-rw-r--r-- | source/benoit/benoit/launcher/parse_arguments.rs | 48 | ||||
-rw-r--r-- | source/benoit/benoit/launcher/print_help.rs | 65 | ||||
-rw-r--r-- | source/benoit/benoit/launcher/print_message.rs | 36 | ||||
-rw-r--r-- | source/benoit/benoit/launcher/run.rs | 60 | ||||
-rw-r--r-- | source/benoit/benoit/launcher/set_title.rs (renamed from source/benoit/benoit/script/set_title.rs) | 6 | ||||
-rw-r--r-- | source/benoit/benoit/launcher/setup.rs | 41 | ||||
-rw-r--r-- | source/benoit/benoit/script.rs | 1 | ||||
-rw-r--r-- | source/benoit/benoit/script/dump_frame.rs | 9 | ||||
-rw-r--r-- | source/benoit/main.rs | 52 |
16 files changed, 338 insertions, 93 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index cece66f..daeaf95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# 2.6.3 + +* Add help +* Update readme +* Fix configuration validation +* Fix 'julia' not being respected in configurations +* Improve error handling +* Add launcher structure +* Always set terminal title + # 2.6.2 * Reorder fractal kinds @@ -1,6 +1,6 @@ [package] name = "benoit" -version = "2.6.2" +version = "2.6.3" authors = ["Gabriel Bjørnager Jensen"] edition = "2021" description = "Multithreaded Mandelbrot renderer with support for PNG and WebP encoding." @@ -3,7 +3,7 @@ [*Benoit*](https://mandelbrot.dk/benoit) is a free and open‐source Mandelbrot renderer written in Rust. Its goal is to render arbitrary positions as performant and accurate as possiple. Usage: ``` -benoit [path] +benoit [--help] [path] ``` … where *path* denotes the configuration file to read (optional). If no path is provided, the program is run in *interactive* mode, wherein the fractal is rendered in real‐time. Otherwise, *script* mode is run using the provided configuration. diff --git a/source/benoit/benoit.rs b/source/benoit/benoit.rs index 8292e0c..b7878b2 100644 --- a/source/benoit/benoit.rs +++ b/source/benoit/benoit.rs @@ -27,6 +27,7 @@ pub mod complex; pub mod configuration; pub mod image; pub mod fractal; +pub mod launcher; pub mod palette; pub mod render; pub mod render_data; @@ -36,7 +37,7 @@ pub mod video; pub const VERSION: (u32, u32, u32) = ( 0x2, // Major 0x6, // Minor - 0x2, // Patch + 0x3, // Patch ); pub const PRECISION: u32 = 0x80; diff --git a/source/benoit/benoit/configuration/load.rs b/source/benoit/benoit/configuration/load.rs index a993a4d..8c02be9 100644 --- a/source/benoit/benoit/configuration/load.rs +++ b/source/benoit/benoit/configuration/load.rs @@ -37,7 +37,7 @@ use toml::{Table, Value}; impl Configuration { #[must_use] - pub fn load(path: &str) -> Configuration { + pub fn load(path: &str) -> Result<Configuration, String> { eprintln!("loading configuration at \"{path}\""); let mut configuration = Configuration::default(); @@ -51,18 +51,8 @@ impl Configuration { get_integer(&mut configuration.thread_count, &configuration_table, "thread_count"); - if let Some(name) = get_string(&configuration_table, "fractal") { - configuration.fractal.kind = match FractalKind::from_str(name.as_str()) { - Ok(kind) => kind, - Err(message) => panic!("{message}"), - }; - } - - { - let mut inverse = false; - get_boolean(&mut inverse, &configuration_table, "inverse"); - configuration.fractal.inverse = inverse; - } + get_boolean(&mut configuration.fractal.inverse, &configuration_table, "inverse"); + get_boolean(&mut configuration.fractal.julia, &configuration_table, "julia"); get_integer(&mut configuration.canvas_width, &configuration_table, "canvas_width"); get_integer(&mut configuration.canvas_height, &configuration_table, "canvas_height"); @@ -77,41 +67,37 @@ impl Configuration { get_integer(&mut configuration.max_iter_count, &configuration_table, "maximum_iteration_count"); - if let Some(name) = get_string(&configuration_table, "palette") { - configuration.palette = match Palette::from_str(name.as_str()) { - Ok(palette) => palette, - Err(message) => panic!("{message}"), - }; + get_float(&mut configuration.colour_range, &configuration_table, "colour_range"); + + // Enumerations: + if let Some(name) = get_string(&configuration_table, "fractal") { + configuration.fractal.kind = FractalKind::from_str(name.as_str())?; } - get_float(&mut configuration.colour_range, &configuration_table, "colour_range"); + if let Some(name) = get_string(&configuration_table, "palette") { + configuration.palette = Palette::from_str(name.as_str())?; + } if let Some(path) = get_string(&configuration_table, "dump_path") { configuration.dump_path = path.clone(); } if let Some(name) = get_string(&configuration_table, "image_format") { - configuration.image_format = match ImageFormat::from_str(name.as_str()) { - Ok(format) => format, - Err(message) => panic!("{message}"), - }; + configuration.image_format = ImageFormat::from_str(name.as_str())?; } - match check_configuration(&configuration) { - Err(message) => panic!("invalid configuration: {message}"), - _ => {}, - } + validate_configuration(&configuration)?; - return configuration; + return Ok(configuration); } } -fn check_configuration(configuration: &Configuration) -> Result<(), &str> { +fn validate_configuration(configuration: &Configuration) -> Result<(), &str> { // We allow thread counts of zero as those signal // automatic thread count detection. - if configuration.canvas_width == 0x0 { - return Err("only non-zero values for canvas_width are allowed"); + if configuration.canvas_width == 0x0 || configuration.canvas_height == 0x0 { + return Err("only non-zero values for canvas_width and canvas_height are allowed"); } if configuration.scale == 0x0 { @@ -126,6 +112,10 @@ fn check_configuration(configuration: &Configuration) -> Result<(), &str> { return Err("only non-zero values for maximum_iteration_count are allowed"); } + if configuration.colour_range < 2.0 { + return Err("only values greater than two are allowed for colour_range"); + } + return Ok(()); } diff --git a/source/benoit/benoit/image/dump.rs b/source/benoit/benoit/image/dump.rs index 18c166c..1e029c4 100644 --- a/source/benoit/benoit/image/dump.rs +++ b/source/benoit/benoit/image/dump.rs @@ -31,9 +31,11 @@ use std::io::BufWriter; impl Image { pub fn dump(&self, path: &str, format: ImageFormat) { + use ImageFormat::*; + match format { - ImageFormat::Png => self.dump_png( path), - ImageFormat::Webp => self.dump_webp(path), + Png => self.dump_png( path), + Webp => self.dump_webp(path), } } diff --git a/source/benoit/benoit/launcher.rs b/source/benoit/benoit/launcher.rs new file mode 100644 index 0000000..3a5ea48 --- /dev/null +++ b/source/benoit/benoit/launcher.rs @@ -0,0 +1,38 @@ +/* + Copyright 2021, 2023 Gabriel Bjørnager Jensen. + + This file is part of Benoit. + + Benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +pub mod parse_arguments; +pub mod print_help; +pub mod print_message; +pub mod run; +pub mod set_title; +pub mod setup; + +pub struct Launcher {} + +impl Launcher { + #[must_use] + pub fn new() -> Launcher { + return Launcher {}; + } +} diff --git a/source/benoit/benoit/launcher/parse_arguments.rs b/source/benoit/benoit/launcher/parse_arguments.rs new file mode 100644 index 0000000..73d2d79 --- /dev/null +++ b/source/benoit/benoit/launcher/parse_arguments.rs @@ -0,0 +1,48 @@ +/* + Copyright 2021, 2023 Gabriel Bjørnager Jensen. + + This file is part of Benoit. + + Benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::benoit::configuration::Configuration; +use crate::benoit::launcher::Launcher; + +use std::env::args; + +impl Launcher { + #[must_use] + pub(super) fn parse_arguments(&self) -> (Configuration, bool) { + let mut arguments = args(); + + return match arguments.nth(0x1) { + Some(argument) => { + if argument == "--help" { Launcher::print_help() }; + + let configuration = match Configuration::load(argument.as_str()) { + Ok( configuration) => configuration, + Err(message) => panic!("error parsing configuration: {message}"), + }; + + (configuration, false) + }, + _ => (Configuration::default(), true), + }; + } +} diff --git a/source/benoit/benoit/launcher/print_help.rs b/source/benoit/benoit/launcher/print_help.rs new file mode 100644 index 0000000..4d03053 --- /dev/null +++ b/source/benoit/benoit/launcher/print_help.rs @@ -0,0 +1,65 @@ +/* + Copyright 2021, 2023 Gabriel Bjørnager Jensen. + + This file is part of Benoit. + + Benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::benoit::launcher::Launcher; + +use std::process::exit; + +impl Launcher { + pub(super) fn print_help() -> ! { + println!("Usage:"); + println!(" benoit [--help] [path]"); + println!(); + println!("Configuration:"); + println!(" thread_count 0..=4294967295"); + println!(); + println!(" fractal \"mandelbrot\"|\"multibrot3\"|\"multibrot4\"|\"burningship\"|\"tricorn\""); + println!(" inverse false|true"); + println!(" julia false|true"); + println!(); + println!(" canvas_width 0..=4294967295"); + println!(" canvas_height 0..=4294967295"); + println!(" frame_start 0..=4294967295"); + println!(" frame_stop frame_start..=4294967295"); + println!(); + println!(" real \"0.0\"\u{B1}"); + println!(" imaginary \"0.0\"\u{B1}"); + println!(" extra_real \"0.0\"\u{B1}"); + println!(" extra_imaginary \"0.0\"\u{B1}"); + println!(" zoom \"0.0\"<"); + println!(); + println!(" maximum_iteration_count 1..=4294967295"); + println!(); + println!(" palette \"emerald\"|\"fire\"|\"greyscale\"|\"hsv\"|\"lch\"|\"ruby\"|\"sapphire\"|\"simple\"|\"twilight\""); + println!(" colour_range 2.0+"); + println!(); + println!(" dump_path"); + println!(" image_format \"png\"|\"webp\""); + println!(); + println!("Note that real, imaginary, extra_real, extra_imaginary, and zoom - if present - must be quoted floats."); + println!("When choosing image format, keep in mind that PNG is faster whilst WebP yields better compression. Both are, however, lossless."); + println!(); + + exit(0x0); + } +} diff --git a/source/benoit/benoit/launcher/print_message.rs b/source/benoit/benoit/launcher/print_message.rs new file mode 100644 index 0000000..5f9314e --- /dev/null +++ b/source/benoit/benoit/launcher/print_message.rs @@ -0,0 +1,36 @@ +/* + Copyright 2021, 2023 Gabriel Bjørnager Jensen. + + This file is part of Benoit. + + Benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::benoit::VERSION; +use crate::benoit::launcher::Launcher; + +impl Launcher { + pub(super) fn print_message() { + println!(); + println!(" \u{1B}[1mBENO\u{CE}T\u{1B}[0m {:X}.{:X}.{:X}", VERSION.0, VERSION.1, VERSION.2); + println!(" Copyright 2021, 2023 \u{1B}[1mGabriel Bj\u{F8}rnager Jensen\u{1B}[0m."); + println!(); + println!(" \u{1B}[3mLe p\u{E8}re cogita et c'est pourquoi il fut.\u{1B}[0m"); + println!(); + } +} diff --git a/source/benoit/benoit/launcher/run.rs b/source/benoit/benoit/launcher/run.rs new file mode 100644 index 0000000..f949e6a --- /dev/null +++ b/source/benoit/benoit/launcher/run.rs @@ -0,0 +1,60 @@ +/* + Copyright 2021, 2023 Gabriel Bjørnager Jensen. + + This file is part of Benoit. + + Benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::benoit::app::App; +use crate::benoit::launcher::Launcher; +use crate::benoit::script::Script; + +use std::thread::available_parallelism; + +impl Launcher { + #[must_use] + pub fn run(self) -> i32 { + Launcher::print_message(); + + let (mut configuration, interative) = self.parse_arguments(); + + configuration.thread_count = if configuration.thread_count == 0x0 { + match available_parallelism() { + Ok(ammount) => ammount.get() as u32, + _ => 0x2, // We assume at least two threads. + } + } else { + configuration.thread_count + }; + + self.setup(configuration.thread_count); + + return if interative { + eprintln!("running in iteractive mode"); + + let app = App::configure(configuration); + app.run() + } else { + eprintln!("running in script mode"); + + let script = Script::configure(configuration); + script.run() + }; + } +} diff --git a/source/benoit/benoit/script/set_title.rs b/source/benoit/benoit/launcher/set_title.rs index b8bcb56..8066369 100644 --- a/source/benoit/benoit/script/set_title.rs +++ b/source/benoit/benoit/launcher/set_title.rs @@ -21,7 +21,7 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::script::Script; +use crate::benoit::launcher::Launcher; #[cfg(windows)] extern crate windows; @@ -29,8 +29,8 @@ extern crate windows; #[cfg(windows)] use windows::Win32::System::Console::SetConsoleTitleA; -impl Script { - pub(super) fn set_title(title: &str) { +impl Launcher { + pub fn set_title(title: &str) { #[cfg(unix)] { eprint!("\u{1B}]0;{title}\u{07}") }; diff --git a/source/benoit/benoit/launcher/setup.rs b/source/benoit/benoit/launcher/setup.rs new file mode 100644 index 0000000..3e9be56 --- /dev/null +++ b/source/benoit/benoit/launcher/setup.rs @@ -0,0 +1,41 @@ +/* + Copyright 2021, 2023 Gabriel Bjørnager Jensen. + + This file is part of Benoit. + + Benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::benoit::VERSION; +use crate::benoit::launcher::Launcher; +use crate::benoit::palette::fill_palettes; + +extern crate rayon; + +use rayon::ThreadPoolBuilder; + +impl Launcher { + pub(super) fn setup(&self, thread_count: u32) { + Launcher::set_title(format!("BENO\u{CE}T {:X}.{:X}.{:X}", VERSION.0, VERSION.1, VERSION.2).as_str()); + + fill_palettes(); + + eprintln!("using {} threads", thread_count); + ThreadPoolBuilder::new().num_threads(thread_count as usize).build_global().unwrap(); + } +} diff --git a/source/benoit/benoit/script.rs b/source/benoit/benoit/script.rs index 8cff0ca..2b1a7d0 100644 --- a/source/benoit/benoit/script.rs +++ b/source/benoit/benoit/script.rs @@ -34,7 +34,6 @@ pub mod animate; pub mod configure; pub mod dump_frame; pub mod run; -pub mod set_title; pub mod still; pub struct Script { diff --git a/source/benoit/benoit/script/dump_frame.rs b/source/benoit/benoit/script/dump_frame.rs index 84125e1..c3f1f46 100644 --- a/source/benoit/benoit/script/dump_frame.rs +++ b/source/benoit/benoit/script/dump_frame.rs @@ -24,6 +24,7 @@ use crate::benoit::complex::Complex; use crate::benoit::fractal::Fractal; use crate::benoit::image::{Image, ImageFormat}; +use crate::benoit::launcher::Launcher; use crate::benoit::palette::Palette; use crate::benoit::render::Render; use crate::benoit::script::Script; @@ -49,7 +50,7 @@ impl Script { image_format: ImageFormat, ) { eprint!("\"{name}\" (2^{:.9}x): rendering...", zoom.to_f64().log2()); - Script::set_title("Rendering..."); + Launcher::set_title("Rendering..."); let time_start = Instant::now(); @@ -63,19 +64,19 @@ impl Script { let render_time = time_start.elapsed(); eprint!(" {:.3}ms, colouring...", render_time.as_micros() as f32 / 1000.0); - Script::set_title("Colouring..."); + Launcher::set_title("Colouring..."); image.colour(&render, palette, max_iter_count, colour_range); let colour_time = time_start.elapsed() - render_time; eprint!(" {:.3}ms, dumping...", colour_time.as_micros() as f32 / 1000.0); - Script::set_title("Dumping..."); + Launcher::set_title("Dumping..."); let path = format!("{dump_path}/{name}"); image.dump(path.as_str(), image_format); let dump_time = time_start.elapsed() - colour_time - render_time; eprintln!(" {:.3}ms - \u{1B}[1m\u{1B}[92mdone\u{1B}[0m", dump_time.as_micros() as f32 / 1000.0); - Script::set_title("Done"); + Launcher::set_title("Done"); } } diff --git a/source/benoit/main.rs b/source/benoit/main.rs index eb857b8..42822d3 100644 --- a/source/benoit/main.rs +++ b/source/benoit/main.rs @@ -23,59 +23,13 @@ mod benoit; -use crate::benoit::VERSION; -use crate::benoit::app::App; -use crate::benoit::configuration::Configuration; -use crate::benoit::palette::fill_palettes; -use crate::benoit::script::Script; +use crate::benoit::launcher::Launcher; -extern crate rayon; - -use rayon::ThreadPoolBuilder; -use std::env::args; use std::process::exit; -use std::thread::available_parallelism; fn main() { - println!(); - println!(" \u{1B}[1mBENO\u{CE}T\u{1B}[0m {:X}.{:X}.{:X}", VERSION.0, VERSION.1, VERSION.2); - println!(" Copyright 2021, 2023 Gabriel Bj\u{F8}rnager Jensen."); - println!(); - println!(" \u{1B}[3mLe p\u{E8}re cogita et c'est pourquoi il fut.\u{1B}[0m"); - println!(); - - fill_palettes(); - - let mut arguments = args(); - - let (mut configuration, interative) = match arguments.nth(0x1) { - Some(path) => (Configuration::load(path.as_str()), false), - _ => (Configuration::default(), true), - }; - - configuration.thread_count = if configuration.thread_count == 0x0 { - match available_parallelism() { - Ok(ammount) => ammount.get() as u32, - _ => 0x2, // We assume at least two threads. - } - } else { - configuration.thread_count - }; - - eprintln!("using {} threads", configuration.thread_count); - ThreadPoolBuilder::new().num_threads(configuration.thread_count as usize).build_global().unwrap(); - - let code = if interative { - eprintln!("running in iteractive mode"); - - let app = App::configure(configuration); - app.run() - } else { - eprintln!("running in script mode"); - - let script = Script::configure(configuration); - script.run() - }; + let launcher = Launcher::new(); + let code = launcher.run(); exit(code); } |