#![deny(rustdoc::broken_intra_doc_links, rustdoc::private_intra_doc_links)]
pub mod args;
pub mod calls;
pub mod logging;
pub mod music;
pub mod toml_file;
pub mod utils;
use std::{
path::Path,
str::FromStr,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
time::{Duration, Instant},
};
use log::LevelFilter;
use monument::{Composition, Search};
use ordered_float::OrderedFloat;
use ringing_utils::PrettyDuration;
use simple_logger::SimpleLogger;
use toml_file::TomlFile;
use crate::logging::{CompositionPrinter, SingleLineProgressLogger};
pub fn init_logging(filter: LevelFilter) {
SimpleLogger::new()
.without_timestamps()
.with_colors(true)
.with_level(filter)
.init()
.unwrap();
}
pub fn run(
toml_path: &Path,
options: &args::Options,
env: Environment,
) -> anyhow::Result<Option<SearchResult>> {
macro_rules! debug_print {
($variant: ident, $val: expr) => {
if options.debug_option == Some(DebugOption::$variant) {
dbg!($val);
return Ok(None);
}
};
}
let start_time = Instant::now();
let toml_file = TomlFile::new(toml_path)?;
debug_print!(Toml, toml_file);
let leak_search_memory = env == Environment::Cli;
let (params, music_displays) = toml_file.to_params(toml_path)?;
debug_print!(Params, params);
let search = Arc::new(Search::new(
params.clone(),
toml_file.config(options, leak_search_memory),
)?);
debug_print!(Search, search);
let comp_printer = CompositionPrinter::new(
music_displays,
search.clone(),
toml_file.should_print_atw(),
!options.dont_display_comp_numbers,
);
let mut update_logger = SingleLineProgressLogger::new(match options.only_display_update_line {
true => None,
false => Some(comp_printer.clone()),
});
if options.debug_option == Some(DebugOption::StopBeforeSearch) {
return Ok(None);
}
let abort_flag = Arc::new(AtomicBool::new(false));
if env == Environment::Cli {
let abort_flag = Arc::clone(&abort_flag);
if let Err(e) = ctrlc::set_handler(move || abort_flag.store(true, Ordering::SeqCst)) {
log::warn!("Error setting ctrl-C handler: {}", e);
}
}
let mut comps = Vec::<(Composition, usize)>::new();
search.run(
|update| {
let next_comp_number = comps.len();
if let Some(comp) = update_logger.log(update, next_comp_number) {
comps.push((comp, next_comp_number));
}
},
&abort_flag,
);
fn rounded_float(f: f32) -> OrderedFloat<f32> {
const FACTOR: f32 = 1e-6;
let rounded = (f / FACTOR).round() * FACTOR;
OrderedFloat(rounded)
}
comps.sort_by_cached_key(|(comp, _generation_index)| {
(
rounded_float(comp.music_score(¶ms)),
rounded_float(comp.average_score()),
comp.call_string(¶ms),
)
});
Ok(Some(SearchResult {
comps,
comp_printer,
duration: start_time.elapsed(),
aborted: abort_flag.load(Ordering::SeqCst),
search,
}))
}
#[derive(Debug, PartialEq, Eq)]
pub enum Environment {
TestHarness,
Cli,
}
#[derive(Debug, Clone)]
pub struct SearchResult {
pub comps: Vec<(Composition, usize)>,
pub search: Arc<Search>,
pub duration: Duration,
pub aborted: bool,
comp_printer: self::logging::CompositionPrinter,
}
impl SearchResult {
pub fn print(&mut self) {
eprintln!("\n\n\n\nSEARCH COMPLETE!\n\n\n");
for (c, generation_index) in &self.comps {
println!(
"{}",
self.comp_printer
.comp_string_with_possible_headers(c, *generation_index)
);
}
println!("{}", self.comp_printer.footer_lines());
eprintln!(
"{} composition{} generated{} {}",
self.comps.len(),
if self.comps.len() == 1 { "" } else { "s" }, match self.aborted {
true => ", aborted after",
false => " in",
},
PrettyDuration(self.duration)
);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DebugOption {
Toml,
Params,
Search,
Graph,
StopBeforeSearch,
}
impl FromStr for DebugOption {
type Err = String;
fn from_str(v: &str) -> Result<Self, String> {
Ok(match v.to_lowercase().as_str() {
"toml" => Self::Toml,
"params" => Self::Params,
"search" => Self::Search,
"graph" => Self::Graph,
"no-search" => Self::StopBeforeSearch,
#[rustfmt::skip] _ => return Err(format!(
"Unknown value {:?}. Expected `toml`, `params`, `search`, `graph` or `no-search`.",
v
)),
})
}
}