use itertools::Itertools;
use crate::fixed_point;
use crate::supply::SupplyBound;
use crate::time::{Duration, Offset, Service};
use crate::arrival::ArrivalBound;
use crate::wcet::JobCostModel;
pub use super::rr::{is_higher_callback_priority_than, CallbackType};
const EPSILON: Duration = Duration::epsilon();
const EPSILON_SERVICE: Service = Service::in_interval(EPSILON);
pub struct Callback<'a, 'b, AB: ArrivalBound + ?Sized, CM: JobCostModel + ?Sized> {
response_time_bound: Duration,
arrival_bound: &'a AB,
cost_model: &'b CM,
kind: CallbackType,
}
impl<'a, 'b, AB: ArrivalBound + ?Sized, CM: JobCostModel + ?Sized> Callback<'a, 'b, AB, CM> {
pub fn new(
response_time_bound: Duration,
arrival_bound: &'a AB,
cost_model: &'b CM,
kind: CallbackType,
) -> Callback<'a, 'b, AB, CM> {
Callback {
response_time_bound,
arrival_bound,
cost_model,
kind,
}
}
fn busy_window_rbf(
&self,
interfered_with: &CallbackType,
delta: Duration,
activation_time: Offset,
num_polling_points: usize,
) -> Service {
let arrived = self.arrival_bound.number_arrivals(delta);
let arrived_bw = self
.arrival_bound
.number_arrivals(activation_time.since_time_zero())
+ num_polling_points;
let n = match self.kind {
CallbackType::Timer | CallbackType::EventSource => arrived,
CallbackType::PolledUnknownPrio => arrived.min(arrived_bw + 1),
CallbackType::Polled(inf_prio) => match *interfered_with {
CallbackType::Polled(ref_prio) => arrived.min(
arrived_bw + is_higher_callback_priority_than(inf_prio, ref_prio) as usize,
),
_ => arrived.min(arrived_bw + 1),
},
};
self.cost_model.cost_of_jobs(n)
}
fn max_self_interfering_instances(&self, activation: Offset) -> usize {
self.arrival_bound
.number_arrivals(activation.closed_since_time_zero())
.saturating_sub(1)
}
fn self_interference_rbf(&self, activation: Offset) -> Service {
self.cost_model
.cost_of_jobs(self.max_self_interfering_instances(activation))
}
#[allow(non_snake_case)]
fn own_workload_rbf(&self, A_star: Offset) -> Service {
let n_activations = self.arrival_bound.number_arrivals(A_star.since_time_zero());
self.cost_model.cost_of_jobs(n_activations)
}
fn marginal_execution_cost(&self, activation: Offset) -> Service {
let n = self.max_self_interfering_instances(activation);
self.cost_model.cost_of_jobs(n + 1) - self.cost_model.cost_of_jobs(n)
}
fn polling_point_bound(&self) -> usize {
self.arrival_bound.number_arrivals(self.response_time_bound)
}
fn subchain_polling_point_bound(subchain: &[&Callback<AB, CM>]) -> usize {
subchain.iter().map(|cb| cb.polling_point_bound()).sum()
}
}
#[allow(non_snake_case)]
pub fn rta_subchain<SBF, AB, CM>(
supply: &SBF,
workload: &[Callback<AB, CM>],
subchain: &[&Callback<AB, CM>],
limit: Duration,
) -> fixed_point::SearchResult
where
SBF: SupplyBound + ?Sized,
AB: ArrivalBound + ?Sized,
CM: JobCostModel + ?Sized,
{
let max_num_polling_points = Callback::subchain_polling_point_bound(subchain);
let eoc = subchain.last().expect("subchain must not be empty");
let singleton_chain = subchain.len() == 1;
debug_assert!(
subchain
.iter()
.all(|sc_cb| workload.iter().any(|wl_cb| std::ptr::eq(*sc_cb, wl_cb))),
"subchain not wholly part of workload"
);
let bw_interference = |delta: Duration, activation: Offset| {
workload
.iter()
.map(|cb| {
if std::ptr::eq(*eoc, cb) {
Service::none()
} else {
cb.busy_window_rbf(&eoc.kind, delta, activation, max_num_polling_points)
}
})
.sum()
};
let rta = |activation: Offset| -> fixed_point::SearchResult {
let si = eoc.self_interference_rbf(activation);
let rhs_S_star = |S_star: Duration| {
let di = bw_interference(S_star, activation);
EPSILON_SERVICE + di + si
};
let S_star = fixed_point::search(supply, limit, rhs_S_star)?;
let supply_star = supply.provided_service(S_star);
let omega = eoc.marginal_execution_cost(activation);
let rhs_F_star = supply_star.saturating_sub(EPSILON_SERVICE) + omega;
let F_star = supply.service_time(rhs_F_star);
if singleton_chain {
Ok(F_star.saturating_sub(activation.since_time_zero()))
} else {
Ok(F_star)
}
};
let rhs_max = |ta_star: Duration| {
let si = eoc.own_workload_rbf(Offset::from_time_zero(ta_star));
let di = bw_interference(ta_star, Offset::from_time_zero(ta_star));
EPSILON_SERVICE + di + si
};
let max_offset = Offset::from_time_zero(fixed_point::search(supply, limit, rhs_max)?);
let all_steps = workload
.iter()
.filter(|cb| cb.kind.is_pp() || std::ptr::eq(*eoc, *cb))
.map(|cb| {
cb.arrival_bound.steps_iter().map(move |delta| {
if std::ptr::eq(*eoc, cb) {
Offset::from_time_zero(delta.saturating_sub(EPSILON))
} else {
Offset::from_time_zero(delta)
}
})
})
.kmerge()
.dedup();
#[cfg(debug_assertions)]
let mut brute_force_steps = (0..)
.filter(|t_a| {
workload.iter().any(|cb|
if std::ptr::eq(*eoc, cb) {
cb.arrival_bound.number_arrivals(Duration::from(*t_a)) !=
cb.arrival_bound.number_arrivals(Duration::from(*t_a + 1))
} else {
cb.kind.is_pp() && *t_a > 0 &&
cb.arrival_bound.number_arrivals(Duration::from(*t_a - 1)) !=
cb.arrival_bound.number_arrivals(Duration::from(*t_a))
}
)
})
.map(Offset::from)
.peekable();
#[cfg(debug_assertions)]
let all_steps = {
let mut wrapped = all_steps.peekable();
assert_eq!(brute_force_steps.peek(), wrapped.peek());
wrapped.zip(brute_force_steps).map(|(a, bf)| {
assert_eq!(a, bf);
a
})
};
let search_space = all_steps.take_while(|activation| *activation < max_offset);
fixed_point::max_response_time(search_space.map(rta))
}