use scv_protocol::{JobChange, JobOutcome};
#[derive(Debug, Default)]
pub(super) struct Owed {
owed: Vec<Owing>,
}
#[derive(Debug)]
struct Owing {
job: String,
pending: Option<JobOutcome>,
last: Option<JobOutcome>,
via: Via,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Via {
Waiting(String),
Change(String),
Sending { call: String, last: bool },
Update,
Updating,
}
impl Owed {
fn find(&mut self, job: &str, via: &Via) -> Option<&mut Owing> {
self.owed
.iter_mut()
.find(|owing| owing.job == job && &owing.via == via)
}
pub(super) fn jobs(&self) -> impl Iterator<Item = &str> {
self.owed.iter().map(|owing| owing.job.as_str())
}
pub(super) fn cancelling(&mut self, job: &str, call: &str) {
self.owed.push(Owing {
job: job.to_owned(),
pending: None,
last: None,
via: Via::Waiting(call.to_owned()),
});
}
pub(super) fn waited(
&mut self,
job: &str,
call: &str,
pending: Option<JobOutcome>,
) -> Option<JobOutcome> {
let owing = self.find(job, &Via::Waiting(call.to_owned()))?;
owing.pending = pending;
owing.via = Via::Change(call.to_owned());
owing.last.clone().or_else(|| owing.pending.clone())
}
pub(super) fn abandoned(&mut self, job: &str, call: &str) -> bool {
self.find(job, &Via::Waiting(call.to_owned()))
.is_some_and(|owing| {
owing.via = Via::Update;
owing.last.is_some()
})
}
pub(super) fn stopped(&mut self, job: &str, outcome: &JobOutcome) -> bool {
let mut due = false;
for owing in self.owed.iter_mut().filter(|owing| owing.job == job) {
owing.last = Some(outcome.clone());
due |= owing.via == Via::Update;
}
due
}
pub(super) fn take(&mut self, call: &str, change: &mut JobChange) {
if let Some(owing) = self.find(&change.job, &Via::Change(call.to_owned())) {
let last = owing.last.is_some();
change.outcome = owing.last.clone().or_else(|| owing.pending.clone());
owing.via = Via::Sending {
call: call.to_owned(),
last,
};
}
}
pub(super) fn sent(&mut self, call: &str, delivered: bool) -> bool {
let mut due = false;
self.owed.retain_mut(|owing| match &owing.via {
Via::Sending {
call: sending,
last,
} if sending == call => {
if delivered && *last {
return false;
}
owing.via = Via::Update;
due |= owing.last.is_some();
true
}
_ => true,
});
due
}
pub(super) fn evicted(&mut self, call: &str, job: &str) {
if let Some(owing) = self.find(job, &Via::Change(call.to_owned())) {
owing.via = Via::Update;
}
}
pub(super) fn turn_ended(&mut self) -> bool {
let mut due = false;
for owing in &mut self.owed {
if matches!(owing.via, Via::Change(_) | Via::Sending { .. }) {
owing.via = Via::Update;
}
due |= owing.via == Via::Update && owing.last.is_some();
}
due
}
pub(super) fn take_updates(&mut self) -> Vec<JobOutcome> {
let mut due = Vec::new();
for owing in &mut self.owed {
if owing.via == Via::Update
&& let Some(last) = &owing.last
{
due.push(last.clone());
owing.via = Via::Updating;
}
}
due
}
pub(super) fn updated(&mut self, jobs: &[String], delivered: bool) {
self.owed.retain_mut(|owing| {
if owing.via != Via::Updating || !jobs.contains(&owing.job) {
return true;
}
if delivered {
return false;
}
owing.via = Via::Update;
true
});
}
#[cfg(any(test, feature = "test-support"))]
pub(super) fn owe(&mut self, call: &str, change: &JobChange, last: Option<JobOutcome>) {
self.owed.push(Owing {
job: change.job.clone(),
pending: change.outcome.clone(),
last,
via: Via::Change(call.to_owned()),
});
}
}
#[cfg(test)]
mod tests;