340 lines
7.9 KiB
Rust
340 lines
7.9 KiB
Rust
use anyhow::Result;
|
|
use rand::{seq::SliceRandom, Rng};
|
|
use std::{collections::HashSet, path::Path};
|
|
|
|
fn select(variants: &HashSet<String>, weights: &[(u64, String)], rng: &mut impl Rng) -> String {
|
|
let filtered = weights
|
|
.iter()
|
|
.filter(|(_, name)| variants.contains(name))
|
|
.collect::<Vec<_>>();
|
|
|
|
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<String>,
|
|
|
|
#[serde(default)]
|
|
silent: HashSet<String>,
|
|
|
|
#[serde(default)]
|
|
next: Vec<Next>,
|
|
|
|
#[serde(default)]
|
|
depends: Vec<Depend>,
|
|
}
|
|
|
|
impl Entry {
|
|
fn gen<'a, 'b>(
|
|
&'a self,
|
|
weights: Option<&[(u64, String)]>,
|
|
entries: &'a [Entry],
|
|
forbids: &'b mut Vec<Vec<&'a Forbid>>,
|
|
rng: &mut impl Rng,
|
|
) -> String
|
|
where
|
|
'a: 'b,
|
|
{
|
|
let variant = self.gen_variant(weights, rng);
|
|
|
|
if forbids
|
|
.iter()
|
|
.find(|f| {
|
|
f.iter()
|
|
.find(|f| f.name == self.name && f.variants.contains(&variant))
|
|
.is_some()
|
|
})
|
|
.is_some()
|
|
{
|
|
return String::new();
|
|
}
|
|
|
|
let building = self.gen_depends(&variant, entries, forbids, rng);
|
|
|
|
let mut seen = HashSet::new();
|
|
seen.insert(variant.clone());
|
|
self.gen_next(
|
|
building, &variant, weights, entries, forbids, &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::<Vec<_>>()
|
|
.choose(rng)
|
|
.unwrap()
|
|
.to_string()
|
|
}
|
|
|
|
fn gen_depends<'a, 'b>(
|
|
&'a self,
|
|
variant: &str,
|
|
entries: &'a [Entry],
|
|
forbids: &'b mut Vec<Vec<&'a Forbid>>,
|
|
rng: &mut impl Rng,
|
|
) -> String
|
|
where
|
|
'a: 'b,
|
|
{
|
|
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, forbids, rng)
|
|
})
|
|
}
|
|
|
|
fn gen_next<'a, 'b>(
|
|
&'a self,
|
|
building: String,
|
|
variant: &str,
|
|
weights: Option<&[(u64, String)]>,
|
|
entries: &'a [Entry],
|
|
forbids: &'b mut Vec<Vec<&'a Forbid>>,
|
|
seen: &mut HashSet<String>,
|
|
rng: &mut impl Rng,
|
|
) -> String
|
|
where
|
|
'a: 'b,
|
|
{
|
|
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, forbids, rng);
|
|
let building = next.join(acc, &depends);
|
|
|
|
let building =
|
|
self.gen_next(building, &variant, weights, entries, forbids, seen, rng);
|
|
|
|
return (building, variant, seen);
|
|
}
|
|
|
|
(acc, v, seen)
|
|
},
|
|
);
|
|
|
|
s
|
|
}
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
struct Forbid {
|
|
name: String,
|
|
variants: Anyable,
|
|
}
|
|
|
|
#[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<String>,
|
|
weights: Option<&[(u64, String)]>,
|
|
rng: &mut impl Rng,
|
|
) -> Option<String> {
|
|
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::<Vec<_>>()
|
|
.choose(rng)
|
|
.unwrap()
|
|
.to_string(),
|
|
)
|
|
}
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
#[serde(untagged)]
|
|
enum Anyable {
|
|
Specified(HashSet<String>),
|
|
|
|
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<Vec<(u64, String)>>,
|
|
default: Option<String>,
|
|
|
|
#[serde(default)]
|
|
forbids: Vec<Forbid>,
|
|
}
|
|
|
|
impl Depend {
|
|
fn gen<'a, 'b>(
|
|
&'a self,
|
|
acc: String,
|
|
variant: &str,
|
|
entries: &'a [Entry],
|
|
forbids: &'b mut Vec<Vec<&'a Forbid>>,
|
|
rng: &mut impl Rng,
|
|
) -> String
|
|
where
|
|
'a: 'b,
|
|
{
|
|
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 local_forbids = self.forbids.iter().collect();
|
|
|
|
forbids.push(local_forbids);
|
|
|
|
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,
|
|
forbids,
|
|
rng,
|
|
);
|
|
|
|
forbids.pop();
|
|
|
|
if acc.is_empty() {
|
|
return value;
|
|
}
|
|
|
|
if value.is_empty() {
|
|
return acc;
|
|
}
|
|
|
|
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<Entry>,
|
|
}
|
|
|
|
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));
|
|
|
|
let mut forbids = Vec::new();
|
|
|
|
root.gen(None, &self.entries, &mut forbids, rng)
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn config(path: impl AsRef<Path>) -> Result<Config> {
|
|
let bytes = tokio::fs::read(path).await?;
|
|
let config: Config = serde_json::from_slice(&bytes)?;
|
|
|
|
Ok(config)
|
|
}
|