use std::collections::{BTreeMap, BTreeSet};
use std::sync::{Arc, Mutex, PoisonError};
use super::{ReloadOutcome, ReloadReport, ReloadSubject};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Latest {
pub seq: u64,
pub outcome: ReloadOutcome,
}
#[derive(Debug, Clone, Default)]
pub(crate) struct ReportBoard {
seq: u64,
armed_at: Option<u64>,
live: BTreeSet<ReloadSubject>,
latest: BTreeMap<ReloadSubject, Latest>,
}
impl ReportBoard {
pub(crate) fn latest(&self, subject: &ReloadSubject) -> Option<&Latest> {
self.latest.get(subject)
}
pub(crate) fn armed_at(&self) -> Option<u64> {
self.armed_at
}
pub(crate) fn is_live(&self, subject: &ReloadSubject) -> Option<bool> {
self.armed_at.map(|_| self.live.contains(subject))
}
fn next(&mut self) -> u64 {
self.seq += 1;
self.seq
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct ReloadReports(Arc<Mutex<ReportBoard>>);
impl ReloadReports {
pub(crate) fn arm(&self, subjects: impl IntoIterator<Item = ReloadSubject>) {
let mut board = self.lock();
let seq = board.next();
board.armed_at = Some(seq);
board.live = subjects.into_iter().collect();
board.latest.clear();
}
pub(crate) fn publish(&self, reports: &[ReloadReport]) {
let mut board = self.lock();
for report in reports {
let seq = board.next();
board.latest.insert(
report.subject.clone(),
Latest {
seq,
outcome: report.outcome.clone(),
},
);
}
}
pub(crate) fn seq(&self) -> u64 {
self.lock().seq
}
pub(crate) fn snapshot(&self) -> ReportBoard {
self.lock().clone()
}
fn lock(&self) -> std::sync::MutexGuard<'_, ReportBoard> {
self.0.lock().unwrap_or_else(PoisonError::into_inner)
}
}
#[cfg(test)]
mod tests {
use super::super::ReloadFailure;
use super::*;
fn report(subject: ReloadSubject, outcome: ReloadOutcome) -> ReloadReport {
ReloadReport { subject, outcome }
}
fn failed(why: &str) -> ReloadOutcome {
ReloadOutcome::Failed(ReloadFailure::Unstarted(why.to_string()))
}
fn applies() -> ReloadOutcome {
ReloadOutcome::AppliesOnLoad {
warnings: Vec::new(),
}
}
fn shader(name: &str) -> ReloadSubject {
ReloadSubject::shader(name)
}
#[test]
fn each_subject_keeps_its_newest_outcome() {
let reports = ReloadReports::default();
let reader = reports.clone();
reports.arm([shader("lit"), shader("water")]);
let armed = reader.seq();
reports.publish(&[
report(shader("lit"), failed("first")),
report(shader("water"), applies()),
]);
let before = reader.snapshot().latest(&shader("lit")).unwrap().seq;
reports.publish(&[report(shader("lit"), applies())]);
let board = reader.snapshot();
let lit = board.latest(&shader("lit")).unwrap();
assert_eq!(lit.outcome, applies());
assert!(lit.seq > before);
assert!(lit.seq > board.latest(&shader("water")).unwrap().seq);
assert!(board.latest(&shader("cave")).is_none());
assert_eq!(reader.seq(), lit.seq, "the latest change");
assert!(reader.seq() > armed);
}
#[test]
fn arming_starts_a_fresh_board() {
let reports = ReloadReports::default();
assert_eq!(reports.snapshot().is_live(&shader("lit")), None);
assert_eq!(reports.snapshot().armed_at(), None);
reports.arm([shader("lit")]);
reports.publish(&[report(shader("lit"), failed("broken"))]);
let published = reports.snapshot().latest(&shader("lit")).unwrap().seq;
reports.arm([shader("lit"), shader("new")]);
let board = reports.snapshot();
assert!(board.latest(&shader("lit")).is_none());
assert!(board.armed_at().unwrap() > published);
assert_eq!(board.is_live(&shader("new")), Some(true));
assert_eq!(board.is_live(&shader("gone")), Some(false));
}
#[test]
fn a_shader_and_a_volume_of_one_name_are_two_subjects() {
let reports = ReloadReports::default();
let volume = ReloadSubject::sdf_volume("water");
reports.arm([shader("water"), volume.clone()]);
reports.publish(&[report(volume.clone(), failed("broken"))]);
let board = reports.snapshot();
assert!(board.latest(&shader("water")).is_none());
assert_eq!(board.latest(&volume).unwrap().outcome, failed("broken"));
assert_eq!(board.is_live(&shader("water")), Some(true));
}
}