appcore_filemaker/
control.rs1use std::sync::atomic::{AtomicBool, Ordering};
12use std::sync::Arc;
13
14use serde::{Deserialize, Serialize};
15
16use crate::{ErrorCode, FileMakerError, Result};
17
18#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
20#[serde(rename_all = "snake_case")]
21pub enum ProgressPhase {
22 Compile,
24 Bind,
26 BindElements,
28 Layout,
30 Reflow,
32 Preflight,
34 Export,
36}
37
38#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
40pub struct ProgressEvent {
41 pub phase: ProgressPhase,
43 pub completed: u64,
45 pub total: Option<u64>,
47}
48
49pub trait ProgressObserver: Send + Sync {
51 fn report(&self, event: &ProgressEvent);
53}
54
55#[derive(Clone, Debug, Default)]
57pub struct CancellationToken(Arc<AtomicBool>);
58
59impl CancellationToken {
60 pub fn cancel(&self) {
62 self.0.store(true, Ordering::Release);
63 }
64
65 #[must_use]
67 pub fn is_cancelled(&self) -> bool {
68 self.0.load(Ordering::Acquire)
69 }
70
71 pub fn check(&self) -> Result<()> {
73 if self.is_cancelled() {
74 return Err(FileMakerError::new(
75 ErrorCode::Cancelled,
76 "operation cancelled at a cooperative boundary",
77 ));
78 }
79 Ok(())
80 }
81}
82
83#[derive(Clone, Default)]
85pub struct OperationControl {
86 cancellation: CancellationToken,
87 observer: Option<Arc<dyn ProgressObserver>>,
88}
89
90impl OperationControl {
91 #[must_use]
93 pub fn new(cancellation: CancellationToken) -> Self {
94 Self {
95 cancellation,
96 observer: None,
97 }
98 }
99
100 #[must_use]
102 pub fn with_observer(mut self, observer: Arc<dyn ProgressObserver>) -> Self {
103 self.observer = Some(observer);
104 self
105 }
106
107 #[must_use]
109 pub fn cancellation(&self) -> &CancellationToken {
110 &self.cancellation
111 }
112
113 pub fn checkpoint(
115 &self,
116 phase: ProgressPhase,
117 completed: u64,
118 total: Option<u64>,
119 ) -> Result<()> {
120 self.cancellation.check()?;
121 if let Some(observer) = &self.observer {
122 observer.report(&ProgressEvent {
123 phase,
124 completed,
125 total,
126 });
127 self.cancellation.check()?;
128 }
129 Ok(())
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn cancellation_is_shared_and_controlled() {
139 let token = CancellationToken::default();
140 let clone = token.clone();
141 token.cancel();
142 assert_eq!(clone.check().unwrap_err().code(), ErrorCode::Cancelled);
143 }
144}