1#![deny(rustdoc::broken_intra_doc_links, rustdoc::private_intra_doc_links)]
7
8pub mod args;
9pub mod calls;
10pub mod logging;
11pub mod music;
12pub mod toml_file;
13pub mod utils;
14
15use std::{
16 path::Path,
17 str::FromStr,
18 sync::{
19 atomic::{AtomicBool, Ordering},
20 Arc,
21 },
22 time::{Duration, Instant},
23};
24
25use log::LevelFilter;
26use monument::{Composition, Search};
27use ordered_float::OrderedFloat;
28use ringing_utils::PrettyDuration;
29use simple_logger::SimpleLogger;
30use toml_file::TomlFile;
31
32use crate::logging::{CompositionPrinter, SingleLineProgressLogger};
33
34pub fn init_logging(filter: LevelFilter) {
35 SimpleLogger::new()
36 .without_timestamps()
37 .with_colors(true)
38 .with_level(filter)
39 .init()
40 .unwrap();
41}
42
43pub fn run(
44 toml_path: &Path,
45 options: &args::Options,
46 env: Environment,
47) -> anyhow::Result<Option<SearchResult>> {
48 macro_rules! debug_print {
51 ($variant: ident, $val: expr) => {
52 if options.debug_option == Some(DebugOption::$variant) {
53 dbg!($val);
54 return Ok(None);
55 }
56 };
57 }
58
59 let start_time = Instant::now();
60
61 let toml_file = TomlFile::new(toml_path)?;
63 debug_print!(Toml, toml_file);
64 let leak_search_memory = env == Environment::Cli;
68 let (params, music_displays) = toml_file.to_params(toml_path)?;
70 debug_print!(Params, params);
71 let search = Arc::new(Search::new(
73 params.clone(),
74 toml_file.config(options, leak_search_memory),
75 )?);
76 debug_print!(Search, search);
77
78 let comp_printer = CompositionPrinter::new(
80 music_displays,
81 search.clone(),
82 toml_file.should_print_atw(),
83 !options.dont_display_comp_numbers,
84 );
85 let mut update_logger = SingleLineProgressLogger::new(match options.only_display_update_line {
86 true => None,
87 false => Some(comp_printer.clone()),
88 });
89
90 if options.debug_option == Some(DebugOption::StopBeforeSearch) {
91 return Ok(None);
92 }
93
94 let abort_flag = Arc::new(AtomicBool::new(false));
96 if env == Environment::Cli {
97 let abort_flag = Arc::clone(&abort_flag);
98 if let Err(e) = ctrlc::set_handler(move || abort_flag.store(true, Ordering::SeqCst)) {
99 log::warn!("Error setting ctrl-C handler: {}", e);
100 }
101 }
102
103 let mut comps = Vec::<(Composition, usize)>::new();
105 search.run(
106 |update| {
107 let next_comp_number = comps.len();
108 if let Some(comp) = update_logger.log(update, next_comp_number) {
109 comps.push((comp, next_comp_number));
110 }
111 },
112 &abort_flag,
113 );
114
115 fn rounded_float(f: f32) -> OrderedFloat<f32> {
117 const FACTOR: f32 = 1e-6;
118 let rounded = (f / FACTOR).round() * FACTOR;
119 OrderedFloat(rounded)
120 }
121 comps.sort_by_cached_key(|(comp, _generation_index)| {
122 (
123 rounded_float(comp.music_score(¶ms)),
124 rounded_float(comp.average_score()),
125 comp.call_string(¶ms),
126 )
127 });
128 Ok(Some(SearchResult {
129 comps,
130 comp_printer,
131 duration: start_time.elapsed(),
132 aborted: abort_flag.load(Ordering::SeqCst),
133
134 search,
135 }))
136}
137
138#[derive(Debug, PartialEq, Eq)]
140pub enum Environment {
141 TestHarness,
143 Cli,
145}
146
147#[derive(Debug, Clone)]
148pub struct SearchResult {
149 pub comps: Vec<(Composition, usize)>,
150 pub search: Arc<Search>,
151 pub duration: Duration,
152 pub aborted: bool,
153
154 comp_printer: self::logging::CompositionPrinter,
155}
156
157impl SearchResult {
158 pub fn print(&mut self) {
159 eprintln!("\n\n\n\nSEARCH COMPLETE!\n\n\n");
160 for (c, generation_index) in &self.comps {
161 println!(
162 "{}",
163 self.comp_printer
164 .comp_string_with_possible_headers(c, *generation_index)
165 );
166 }
167 println!("{}", self.comp_printer.footer_lines());
168 eprintln!(
169 "{} composition{} generated{} {}",
170 self.comps.len(),
171 if self.comps.len() == 1 { "" } else { "s" }, match self.aborted {
173 true => ", aborted after",
174 false => " in",
175 },
176 PrettyDuration(self.duration)
177 );
178 }
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub enum DebugOption {
184 Toml,
185 Params,
186 Search,
187 Graph,
188 StopBeforeSearch,
191}
192
193impl FromStr for DebugOption {
194 type Err = String;
195
196 fn from_str(v: &str) -> Result<Self, String> {
197 Ok(match v.to_lowercase().as_str() {
198 "toml" => Self::Toml,
199 "params" => Self::Params,
200 "search" => Self::Search,
201 "graph" => Self::Graph,
202 "no-search" => Self::StopBeforeSearch,
203 #[rustfmt::skip] _ => return Err(format!(
205 "Unknown value {:?}. Expected `toml`, `params`, `search`, `graph` or `no-search`.",
206 v
207 )),
208 })
209 }
210}