wsi_rs/core/
read_control.rs1use std::sync::atomic::{AtomicBool, Ordering};
2use std::sync::{Arc, Mutex};
3use std::time::Duration;
4
5use crate::WsiError;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum DicomIndexMapping {
11 ExtendedOffsetTableDirect,
13 ExtendedOffsetTableItems,
15 BasicOffsetTableItems,
17 SingleFrameItems,
19 OneFragmentPerFrame,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25#[non_exhaustive]
26pub enum DicomIndexOutcome {
27 BuiltFast { mapping: DicomIndexMapping },
29 FastPathFallback,
32 TokenFallback,
34 Reused,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40#[non_exhaustive]
41pub struct DicomIndexDiagnostic {
42 pub outcome: DicomIndexOutcome,
43 pub elapsed: Duration,
44}
45
46impl DicomIndexDiagnostic {
47 #[must_use]
48 pub const fn new(outcome: DicomIndexOutcome, elapsed: Duration) -> Self {
49 Self { outcome, elapsed }
50 }
51}
52
53pub type ReadDiagnosticSink = dyn Fn(DicomIndexDiagnostic) + Send + Sync;
55
56#[derive(Debug, Default)]
58struct ReadCancellationState {
59 cancelled: AtomicBool,
60 publication_gate: Mutex<()>,
61}
62
63#[derive(Debug, Clone, Default)]
64pub struct ReadCancellationToken {
65 state: Arc<ReadCancellationState>,
66}
67
68impl ReadCancellationToken {
69 #[must_use]
70 pub fn new() -> Self {
71 Self::default()
72 }
73
74 pub fn cancel(&self) {
75 let _publication = self
76 .state
77 .publication_gate
78 .lock()
79 .unwrap_or_else(|error| error.into_inner());
80 self.state.cancelled.store(true, Ordering::Release);
81 }
82
83 #[must_use]
84 pub fn is_cancelled(&self) -> bool {
85 self.state.cancelled.load(Ordering::Acquire)
86 }
87}
88
89#[derive(Clone, Default)]
91pub struct ReadControl {
92 cancellation: ReadCancellationToken,
93 diagnostic_sink: Option<Arc<ReadDiagnosticSink>>,
94}
95
96pub(crate) struct DeferredReadDiagnostics {
97 sink: Option<Arc<ReadDiagnosticSink>>,
98 diagnostics: Option<Arc<Mutex<Vec<DicomIndexDiagnostic>>>>,
99}
100
101impl DeferredReadDiagnostics {
102 pub(crate) fn flush(self) {
103 let Some(buffered) = self.diagnostics else {
104 return;
105 };
106 let diagnostics = {
107 let mut guard = buffered.lock().unwrap_or_else(|error| error.into_inner());
108 std::mem::take(&mut *guard)
109 };
110 if let Some(sink) = self.sink {
111 for diagnostic in diagnostics {
112 sink(diagnostic);
113 }
114 }
115 }
116}
117
118impl std::fmt::Debug for ReadControl {
119 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120 formatter
121 .debug_struct("ReadControl")
122 .field("cancellation", &self.cancellation)
123 .field("diagnostics_enabled", &self.diagnostic_sink.is_some())
124 .finish()
125 }
126}
127
128impl ReadControl {
129 #[must_use]
130 pub const fn new(cancellation: ReadCancellationToken) -> Self {
131 Self {
132 cancellation,
133 diagnostic_sink: None,
134 }
135 }
136
137 #[must_use]
140 pub fn with_diagnostic_sink(mut self, sink: Arc<ReadDiagnosticSink>) -> Self {
141 self.diagnostic_sink = Some(sink);
142 self
143 }
144
145 #[must_use]
146 pub fn cancellation(&self) -> &ReadCancellationToken {
147 &self.cancellation
148 }
149
150 #[must_use]
152 pub fn diagnostics_enabled(&self) -> bool {
153 self.diagnostic_sink.is_some()
154 }
155
156 pub fn record_diagnostic(&self, diagnostic: DicomIndexDiagnostic) {
159 if let Some(sink) = &self.diagnostic_sink {
160 sink(diagnostic);
161 }
162 }
163
164 pub(crate) fn defer_diagnostics(&self) -> (Self, DeferredReadDiagnostics) {
165 let Some(sink) = self.diagnostic_sink.as_ref() else {
166 return (
167 self.clone(),
168 DeferredReadDiagnostics {
169 sink: None,
170 diagnostics: None,
171 },
172 );
173 };
174 let diagnostics = Arc::new(Mutex::new(Vec::new()));
175 let diagnostic_sink = {
176 let diagnostics = Arc::clone(&diagnostics);
177 Some(Arc::new(move |diagnostic: DicomIndexDiagnostic| {
178 diagnostics
179 .lock()
180 .unwrap_or_else(|error| error.into_inner())
181 .push(diagnostic);
182 }) as Arc<ReadDiagnosticSink>)
183 };
184 let deferred = DeferredReadDiagnostics {
185 sink: Some(Arc::clone(sink)),
186 diagnostics: Some(diagnostics),
187 };
188 (
189 Self {
190 cancellation: self.cancellation.clone(),
191 diagnostic_sink,
192 },
193 deferred,
194 )
195 }
196
197 pub fn check_cancelled(&self) -> Result<(), WsiError> {
202 if self.cancellation.is_cancelled() {
203 Err(WsiError::Cancelled)
204 } else {
205 Ok(())
206 }
207 }
208
209 pub(crate) fn publish_if_active<T>(&self, publish: impl FnOnce() -> T) -> Result<T, WsiError> {
210 let _publication = self
211 .cancellation
212 .state
213 .publication_gate
214 .lock()
215 .unwrap_or_else(|error| error.into_inner());
216 self.check_cancelled()?;
217 Ok(publish())
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use std::sync::{Arc, Mutex};
224 use std::time::Duration;
225
226 use super::{DicomIndexDiagnostic, DicomIndexMapping, DicomIndexOutcome, ReadControl};
227
228 #[test]
229 fn diagnostic_sink_is_opt_in_and_receives_typed_index_events() {
230 let disabled = ReadControl::default();
231 assert!(!disabled.diagnostics_enabled());
232
233 let events = Arc::new(Mutex::new(Vec::new()));
234 let captured = Arc::clone(&events);
235 let control = ReadControl::default().with_diagnostic_sink(Arc::new(move |event| {
236 captured.lock().unwrap().push(event);
237 }));
238 assert!(control.diagnostics_enabled());
239
240 let diagnostic = DicomIndexDiagnostic {
241 outcome: DicomIndexOutcome::BuiltFast {
242 mapping: DicomIndexMapping::BasicOffsetTableItems,
243 },
244 elapsed: Duration::from_millis(7),
245 };
246 control.record_diagnostic(diagnostic);
247
248 assert_eq!(events.lock().unwrap().as_slice(), &[diagnostic]);
249 }
250}