use crate::bridge::preview_render::PreviewView;
use std::{
collections::HashMap,
sync::{Mutex, PoisonError},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in crate::gui::batch::preview) enum RecordRevision {
Unknown,
Stamp(Option<i64>),
Conflicting,
}
impl RecordRevision {
fn merge(self, other: Self) -> Self {
match (self, other) {
(Self::Unknown, known) | (known, Self::Unknown) => known,
(Self::Stamp(a), Self::Stamp(b)) if a == b => Self::Stamp(a),
_ => Self::Conflicting,
}
}
}
pub(in crate::gui::batch::preview) struct DesignAccum {
front: Option<Vec<u8>>,
top: Option<Vec<u8>>,
remaining: u8,
revision: RecordRevision,
}
pub(super) struct FinishedViews {
pub(super) front: Option<Vec<u8>>,
pub(super) top: Option<Vec<u8>>,
pub(super) revision: RecordRevision,
}
pub(super) fn record_item_result(
design_state: &Mutex<HashMap<i64, DesignAccum>>,
entry_id: i64,
view: PreviewView,
bytes: Option<Vec<u8>>,
revision: RecordRevision,
) -> Option<FinishedViews> {
let mut map = design_state.lock().unwrap_or_else(PoisonError::into_inner);
let accum = map.entry(entry_id).or_insert_with(|| DesignAccum {
front: None,
top: None,
remaining: 2,
revision: RecordRevision::Unknown,
});
match view {
PreviewView::Front => accum.front = bytes,
PreviewView::Top => accum.top = bytes,
}
accum.revision = accum.revision.merge(revision);
accum.remaining = accum.remaining.saturating_sub(1);
if accum.remaining > 0 {
return None;
}
map.remove(&entry_id).map(|done| FinishedViews {
front: done.front,
top: done.top,
revision: done.revision,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn merge_defers_to_the_known_side_and_conflicts_on_unequal_stamps() {
let seen = RecordRevision::Stamp(Some(5));
assert_eq!(RecordRevision::Unknown.merge(seen), seen);
assert_eq!(seen.merge(RecordRevision::Unknown), seen);
assert_eq!(
RecordRevision::Unknown.merge(RecordRevision::Unknown),
RecordRevision::Unknown
);
assert_eq!(seen.merge(RecordRevision::Stamp(Some(5))), seen);
assert_eq!(
seen.merge(RecordRevision::Stamp(Some(6))),
RecordRevision::Conflicting
);
assert_eq!(
seen.merge(RecordRevision::Stamp(None)),
RecordRevision::Conflicting,
"an unstamped row is a different version from a stamped one"
);
assert_eq!(
RecordRevision::Conflicting.merge(seen),
RecordRevision::Conflicting
);
assert_eq!(
seen.merge(RecordRevision::Conflicting),
RecordRevision::Conflicting
);
}
#[test]
fn record_item_result_hands_back_the_pair_once_with_the_merged_revision() {
let state = Mutex::new(HashMap::new());
let revision = RecordRevision::Stamp(Some(5));
assert!(
record_item_result(&state, 7, PreviewView::Top, Some(vec![2]), revision).is_none(),
"the front view is still outstanding"
);
let done = record_item_result(&state, 7, PreviewView::Front, Some(vec![1]), revision)
.expect("both items are in");
assert_eq!(done.front, Some(vec![1]));
assert_eq!(done.top, Some(vec![2]));
assert_eq!(done.revision, revision);
assert!(
state.lock().unwrap().is_empty(),
"a finished design leaves no accumulator behind"
);
}
#[test]
fn items_resolved_at_different_revisions_finish_as_conflicting() {
let state = Mutex::new(HashMap::new());
let _ = record_item_result(
&state,
7,
PreviewView::Front,
Some(vec![1]),
RecordRevision::Stamp(Some(5)),
);
let done = record_item_result(
&state,
7,
PreviewView::Top,
Some(vec![2]),
RecordRevision::Stamp(Some(6)),
)
.expect("both items are in");
assert_eq!(done.revision, RecordRevision::Conflicting);
}
#[test]
fn a_failed_item_without_a_revision_does_not_weaken_the_other_items_stamp() {
let state = Mutex::new(HashMap::new());
let _ = record_item_result(&state, 7, PreviewView::Front, None, RecordRevision::Unknown);
let done = record_item_result(
&state,
7,
PreviewView::Top,
Some(vec![2]),
RecordRevision::Stamp(Some(5)),
)
.expect("both items are in");
assert_eq!(done.front, None);
assert_eq!(done.revision, RecordRevision::Stamp(Some(5)));
}
}