Skip to main content

rsynko_manager/
attention.rs

1use crate::*;
2use alux_ext::ext;
3use rsynko_session::Wanted;
4use std::path::PathBuf;
5use std::time::Duration;
6
7/// States how much of a failure a reader is told about at a glance.
8const SUMMARY_COLUMNS: usize = 160;
9
10/// States how much of a failure is kept for whoever goes looking.
11const DETAIL_COLUMNS: usize = 500;
12
13/// Derives what beginning, running, refusing, and finishing one run states to the collection.
14///
15/// A run is attended to by what the request wants of it, and a request wanting nothing is one
16/// that was removed, or one whose reader is leaving. Which kind of run a request asks for is
17/// something the request already states, so nobody attending to it has to carry that around.
18#[ext(name = ManagerAttentionExt)]
19pub impl<This> This
20where
21    This: QueueCatalogAlg + SafeExitAlg + TransferStateAlg + RehearsalStateAlg,
22    This::Entry: QueueEntryAlg,
23    This::Change: PlannedChangeAlg,
24    This::Id: Copy + Eq,
25{
26    /// States every request waiting for an interpreter to do what it asks, in collection order.
27    fn wanting_work(&self) -> Vec<This::Id> {
28        self.queue_ids()
29            .filter(|id| self.entry_phase(*id) == Some(TransferPhase::Waiting))
30            .collect()
31    }
32
33    /// States what one request wants of the run working on its behalf.
34    fn wanted(&self, id: This::Id) -> Wanted {
35        // Leaving is wanting nothing: every run ends the way a removed request's run ends.
36        if self.exit_requested() {
37            return Wanted::Unwanted;
38        }
39        match self.entry_phase(id) {
40            None => Wanted::Unwanted,
41            Some(TransferPhase::Paused) => Wanted::Held,
42            Some(_) => Wanted::Running,
43        }
44    }
45
46    /// States whether one request asks for a rehearsal rather than for the thing itself.
47    fn rehearses(&self, id: This::Id) -> bool {
48        self.queue_entry(id).and_then(QueueEntryAlg::dry_run) == Some(true)
49    }
50
51    /// States that a run has begun, as the kind of run the request asks for.
52    ///
53    /// A rehearsal moves nothing and is over when it has said what it would do, so it is never
54    /// told that it could be held still.
55    fn begun(&mut self, id: This::Id, holdable: bool) {
56        if self.rehearses(id) {
57            self.apply_rehearsal_event(id, RehearsalObservationOp::Started {});
58            return;
59        }
60        self.apply_transfer_event(id, TransferObservationOp::Started {});
61        self.apply_transfer_event(
62            id,
63            TransferObservationOp::PauseCapability {
64                supported: holdable,
65            },
66        );
67    }
68
69    /// States how long one run has been running, when the request counts that.
70    ///
71    /// A rehearsal moves nothing, so how long it took states nothing about a transfer.
72    fn ran_for(&mut self, id: This::Id, elapsed: Duration) {
73        if !self.rehearses(id) {
74            self.apply_transfer_event(id, TransferObservationOp::Elapsed { elapsed });
75        }
76    }
77
78    /// States that one run refused, as the kind of run the request asks for.
79    fn refused(&mut self, id: This::Id, detail: &str) {
80        if self.rehearses(id) {
81            self.apply_rehearsal_event(
82                id,
83                RehearsalObservationOp::Failed {
84                    message: summary_line(detail),
85                },
86            );
87            return;
88        }
89        self.apply_transfer_event(
90            id,
91            TransferObservationOp::ProgramFailed {
92                summary: summary_line(detail),
93                detail: detail_text(detail),
94            },
95        );
96    }
97
98    /// States what one finished run did, as the kind of run the request asks for.
99    ///
100    /// A rehearsal states what it would have changed. A transfer states that it is done, and how
101    /// much it moved, which is what its changes add up to.
102    fn performed(&mut self, id: This::Id, destination: PathBuf, changes: Vec<This::Change>) {
103        if self.rehearses(id) {
104            self.apply_rehearsal_event(id, RehearsalObservationOp::Reported { changes });
105            return;
106        }
107        let bytes = changes
108            .iter()
109            .filter_map(PlannedChangeAlg::change_size)
110            .fold(0_u64, u64::saturating_add);
111        self.apply_transfer_event(id, TransferObservationOp::Completed { destination, bytes });
112    }
113
114    /// States whether another request is already writing where this one would write.
115    ///
116    /// Two runs writing one path would each publish over the other, and which of them a reader
117    /// ends up with is decided by whichever finished last. The collection knows both, so it is
118    /// the collection that states the clash rather than whoever happens to be running them.
119    fn destination_is_taken(&self, id: This::Id) -> bool {
120        let Some(destination) = self.queue_entry(id).and_then(QueueEntryAlg::output) else {
121            return false;
122        };
123        self.queue_ids()
124            .filter(|other| *other != id)
125            .filter(|other| {
126                self.entry_phase(*other)
127                    .is_some_and(TransferPhase::is_running)
128            })
129            .any(|other| {
130                self.queue_entry(other).and_then(QueueEntryAlg::output) == Some(destination)
131            })
132    }
133
134    /// Observes the phase one request has reached, while the collection still holds it.
135    fn entry_phase(&self, id: This::Id) -> Option<TransferPhase> {
136        self.queue_entry(id).map(QueueEntryAlg::phase)
137    }
138}
139
140/// States the one line a reader is shown about a failure.
141pub fn summary_line(detail: &str) -> String {
142    detail
143        .lines()
144        .next()
145        .unwrap_or_default()
146        .chars()
147        .take(SUMMARY_COLUMNS)
148        .collect()
149}
150
151/// States what is kept about a failure for whoever goes looking for it.
152pub fn detail_text(detail: &str) -> String {
153    detail.chars().take(DETAIL_COLUMNS).collect()
154}