use anyhow::Result; use rand::{seq::SliceRandom, Rng}; use std::{collections::HashSet, path::Path}; fn select(variants: &HashSet, weights: &[(u64, String)], rng: &mut impl Rng) -> String { let filtered = weights .iter() .filter(|(_, name)| variants.contains(name)) .collect::>(); let total = filtered.iter().fold(0, |acc, (weight, _)| acc + weight); let selected = rng.gen_range(0, total); let (s, _) = filtered .iter() .fold((None, selected), |(opt, count), (weight, item)| { if opt.is_some() { return (opt, 0); } let new_count = count.saturating_sub(*weight); if new_count == 0 { return (Some(item), 0); } (None, new_count) }); s.unwrap().to_owned() } #[derive(serde::Deserialize)] struct Entry { name: String, variants: HashSet, #[serde(default)] silent: HashSet, #[serde(default)] next: Vec, #[serde(default)] depends: Vec, } impl Entry { fn gen( &self, weights: Option<&[(u64, String)]>, entries: &[Entry], rng: &mut impl Rng, ) -> String { let variant = self.gen_variant(weights, rng); let building = self.gen_depends(&variant, entries, rng); let mut seen = HashSet::new(); seen.insert(variant.clone()); self.gen_next(building, &variant, weights, entries, &mut seen, rng) } fn gen_variant(&self, weights: Option<&[(u64, String)]>, rng: &mut impl Rng) -> String { if let Some(weights) = weights { return select(&self.variants, &weights, rng); } self.variants .iter() .collect::>() .choose(rng) .unwrap() .to_string() } fn gen_depends(&self, variant: &str, entries: &[Entry], rng: &mut impl Rng) -> String { let base = if self.silent.contains(variant) { String::new() } else { variant.to_string() }; self.depends .iter() .fold(base, |acc, depend| depend.gen(acc, variant, entries, rng)) } fn gen_next( &self, building: String, variant: &str, weights: Option<&[(u64, String)]>, entries: &[Entry], seen: &mut HashSet, rng: &mut impl Rng, ) -> String { let (s, _, _) = self.next.iter().fold( (building, variant.to_string(), seen), |(acc, v, seen), next| { if let Some(variant) = next.gen(&v, &self.variants, weights, rng) { if seen.contains(&variant) { return (acc, variant, seen); } seen.insert(variant.clone()); let depends = self.gen_depends(&variant, entries, rng); let building = next.join(acc, &depends); let building = self.gen_next(building, &variant, weights, entries, seen, rng); return (building, variant, seen); } (acc, v, seen) }, ); s } } #[derive(serde::Deserialize)] struct Next { from: Anyable, to: Anyable, chance: f64, join: String, } impl Next { fn join(&self, building: String, new: &str) -> String { building + &self.join + new } fn gen( &self, variant: &str, variants: &HashSet, weights: Option<&[(u64, String)]>, rng: &mut impl Rng, ) -> Option { if !self.from.contains(variant) { return None; } if !rng.gen_bool(self.chance) { return None; } if let Some(weights) = weights { let hs: HashSet<_> = weights .iter() .filter_map(|(_, s)| { if self.to.contains(s) { Some(s.to_string()) } else { None } }) .collect(); return Some(select(&hs, weights, rng)); } Some( variants .iter() .filter(|s| self.to.contains(s)) .collect::>() .choose(rng) .unwrap() .to_string(), ) } } #[derive(serde::Deserialize)] #[serde(untagged)] enum Anyable { Specified(HashSet), Any(Any), } #[derive(serde::Deserialize)] enum Any { #[serde(rename = "any")] Any, } impl Anyable { fn contains(&self, variant: &str) -> bool { match self { Anyable::Specified(ref hs) => hs.contains(variant), Anyable::Any(_) => true, } } } #[derive(serde::Deserialize)] struct Depend { variants: Anyable, depends: String, chance: f64, order: String, weights: Option>, default: Option, } impl Depend { fn gen(&self, acc: String, variant: &str, entries: &[Entry], rng: &mut impl Rng) -> String { if !self.variants.contains(variant) { return acc; } if !rng.gen_bool(self.chance) { if let Some(default) = self.default.as_ref() { if acc.is_empty() { return default.to_string(); } return self.join(acc, default); } return acc; } let entry = entries .into_iter() .find(|entry| entry.name == self.depends) .expect(&format!("Missing entry for {}", self.depends)); let value = entry.gen(self.weights.as_ref().map(Vec::as_slice), entries, rng); if acc.is_empty() { return value; } self.join(acc, &value) } fn join(&self, acc: String, value: &str) -> String { self.order .replace("{base}", &acc) .replace(&format!("{{{}}}", self.depends), value) } } #[derive(serde::Deserialize)] pub(crate) struct Config { root: String, entries: Vec, } impl Config { pub(crate) fn gen(&self, rng: &mut impl Rng) -> String { let root = self .entries .iter() .find(|entry| entry.name == self.root) .expect(&format!("Invalid config: no entry called {}", self.root)); root.gen(None, &self.entries, rng) } } pub(crate) async fn config(path: impl AsRef) -> Result { let bytes = tokio::fs::read(path).await?; let config: Config = serde_json::from_slice(&bytes)?; Ok(config) }