use actionqueue_core::run::run_instance::RunInstance;
use crate::index::ready::ReadyIndex;
#[derive(Debug, Clone, PartialEq, Eq)]
#[must_use]
pub struct SelectionResult {
selected: Vec<RunInstance>,
remaining: Vec<RunInstance>,
}
impl SelectionResult {
pub fn selected(&self) -> &[RunInstance] {
&self.selected
}
pub fn into_selected(self) -> Vec<RunInstance> {
self.selected
}
pub fn remaining(&self) -> &[RunInstance] {
&self.remaining
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReadyRunSelectionInput {
run: RunInstance,
priority_snapshot: i32,
}
impl ReadyRunSelectionInput {
pub fn run(&self) -> &RunInstance {
&self.run
}
pub fn priority_snapshot(&self) -> i32 {
self.priority_snapshot
}
}
impl ReadyRunSelectionInput {
pub fn new(run: RunInstance, priority_snapshot: i32) -> Self {
Self { run, priority_snapshot }
}
pub fn from_ready_run(run: RunInstance) -> Self {
let priority_snapshot = run.effective_priority();
Self::new(run, priority_snapshot)
}
}
pub fn ready_inputs_from_index(ready: &ReadyIndex) -> Vec<ReadyRunSelectionInput> {
ready.runs().iter().cloned().map(ReadyRunSelectionInput::from_ready_run).collect()
}
pub fn select_ready_runs(ready_runs: &[ReadyRunSelectionInput]) -> SelectionResult {
let mut ready_runs = ready_runs.to_vec();
ready_runs.sort_by(|a, b| {
b.priority_snapshot
.cmp(&a.priority_snapshot)
.then_with(|| a.run.created_at().cmp(&b.run.created_at()))
.then_with(|| a.run.id().cmp(&b.run.id()))
});
let selected = ready_runs.into_iter().map(|ready_run| ready_run.run).collect();
SelectionResult { selected, remaining: Vec::new() }
}
#[cfg(test)]
mod tests {
use actionqueue_core::run::run_instance::RunInstance;
use super::*;
#[test]
fn selects_by_snapshot_priority_descending_then_fifo_for_ties() {
let now = 1000;
let input_low = ReadyRunSelectionInput::new(
RunInstance::new_ready(actionqueue_core::ids::TaskId::new(), now - 10, now + 30, 1)
.expect("valid ready run"),
1,
);
let input_high_later = ReadyRunSelectionInput::new(
RunInstance::new_ready(actionqueue_core::ids::TaskId::new(), now - 10, now + 20, 5)
.expect("valid ready run"),
5,
);
let input_high_earlier = ReadyRunSelectionInput::new(
RunInstance::new_ready(actionqueue_core::ids::TaskId::new(), now - 10, now + 10, 5)
.expect("valid ready run"),
5,
);
let inputs = vec![input_low, input_high_later, input_high_earlier];
let result = select_ready_runs(&inputs);
let ordered_created_at: Vec<u64> =
result.selected().iter().map(|run| run.created_at()).collect();
assert_eq!(ordered_created_at, vec![now + 10, now + 20, now + 30]);
}
#[test]
fn empty_input_returns_empty_selection() {
let result = select_ready_runs(&[]);
assert!(result.selected().is_empty());
assert!(result.remaining().is_empty());
}
#[test]
fn ready_inputs_from_index_captures_run_priority_snapshot() {
let run_a = RunInstance::new_ready(actionqueue_core::ids::TaskId::new(), 100, 200, 11)
.expect("valid ready run");
let run_b = RunInstance::new_ready(actionqueue_core::ids::TaskId::new(), 100, 300, -2)
.expect("valid ready run");
let ready = ReadyIndex::from_runs(vec![run_a.clone(), run_b.clone()]);
let inputs = ready_inputs_from_index(&ready);
assert_eq!(inputs.len(), 2);
assert_eq!(inputs[0].priority_snapshot(), run_a.effective_priority());
assert_eq!(inputs[1].priority_snapshot(), run_b.effective_priority());
assert_eq!(inputs[0].run().id(), run_a.id());
assert_eq!(inputs[1].run().id(), run_b.id());
}
}