melvin_ob/mode_control/mode/
global_mode.rs

1use crate::objective::{BeaconControllerState, KnownImgObjective};
2use crate::scheduling::task::Task;
3use crate::mode_control::{
4    base_mode::BaseMode,
5    mode_context::ModeContext,
6    signal::{ExecExitSignal, OpExitSignal, WaitExitSignal, OptOpExitSignal},
7};
8use crate::{DT_0_STD, fatal, info, log, warn};
9use async_trait::async_trait;
10use chrono::{DateTime, TimeDelta, Utc};
11use std::mem::discriminant;
12use std::{future::Future, pin::Pin, sync::Arc};
13use tokio::{sync::{RwLock, watch::Receiver}, task::JoinError};
14use tokio_util::sync::CancellationToken;
15
16/// Trait representing a high-level operational mode within the onboard Finite-State-Machine (FSM) architecture.
17/// Implementors of [`GlobalMode`] encapsulate full behavioral logic for mode-specific task scheduling,
18/// signal handling, and state transitions.
19#[async_trait]
20pub trait GlobalMode: Sync + Send {
21    /// Returns the rationale string for  finishing the current phase due to safe mode entry.
22    fn safe_mode_rationale(&self) -> &'static str { "SAFE mode Event!" }
23    /// Returns the rationale string for finishing the current phase due to a new Zoned Objective.
24    fn new_zo_rationale(&self) -> &'static str { "newly discovered ZO!" }
25    /// Returns the rationale string for finishing the current phase due to a new Beacon Objective.
26    fn new_bo_rationale(&self) -> &'static str { "newly discovered BO!" }
27    /// Returns the rationale string used when the task queue has completed.
28    fn tasks_done_rationale(&self) -> &'static str { "tasks list done!" }
29    /// Returns the rationale for finishing the task queue and exiting the orbit for ZO Retrieval.
30    fn tasks_done_exit_rationale(&self) -> &'static str {
31        "tasks list done and exited orbit for ZO Retrieval!"
32    }
33    /// Returns the rationale for finishing the current phase due to being outside of orbit without a valid reason.
34    fn out_of_orbit_rationale(&self) -> &'static str { "out of orbit without purpose!" }
35    /// Returns the rationale used for finishing the current phase when a beacon objective has been completed or expired.
36    fn bo_done_rationale(&self) -> &'static str { "BO done or expired!" }
37
38    /// Returns the string representation of the current mode.
39    fn type_name(&self) -> &'static str;
40
41    /// Initializes the mode with the provided context.
42    ///
43    /// # Arguments
44    /// * `context` - Shared reference to the current mode context.
45    ///
46    /// # Returns
47    /// [`OpExitSignal`] - Signal indicating what action to take after initialization.
48    async fn init_mode(&self, context: Arc<ModeContext>) -> OpExitSignal;
49
50    /// Executes all tasks in the current task queue in sequence.
51    /// Waits for each task’s scheduled time and handles early exit signals such as safe transitions or new objectives.
52    ///
53    /// # Arguments
54    /// * `context` - Shared reference to the current mode context.
55    ///
56    /// # Returns
57    /// * [`OpExitSignal`] - Signal indicating whether to continue or exit the mode.
58    #[allow(clippy::cast_sign_loss, clippy::cast_precision_loss)]
59    async fn exec_task_queue(&self, context: Arc<ModeContext>) -> OpExitSignal {
60        let context_local = Arc::clone(&context);
61        let mut tasks = 0;
62        while let Some(task) = {
63            let sched_arc = context_local.k().t_cont().sched_arc();
64            let mut sched_lock = sched_arc.write().await;
65            let t = sched_lock.pop_front();
66            drop(sched_lock);
67            t
68        } {
69            let due_time = task.t() - Utc::now();
70            let task_type = task.task_type();
71            info!("TASK {tasks}: {task_type} in  {}s!", due_time.num_seconds());
72            while task.t() > Utc::now() + TimeDelta::seconds(2) {
73                let context_clone = Arc::clone(&context);
74                match self.exec_task_wait(context_clone, task.t()).await {
75                    WaitExitSignal::Continue => {}
76                    WaitExitSignal::SafeEvent => {
77                        return self.safe_handler(context_local).await;
78                    }
79                    WaitExitSignal::NewZOEvent(obj) => {
80                        if let Some(opt) = self.zo_handler(&context, obj).await {
81                            return opt;
82                        };
83                    }
84                    WaitExitSignal::BOEvent => {
85                        if let Some(opt) = self.bo_event_handler(&context).await {
86                            return opt;
87                        };
88                    }
89                };
90            }
91            let task_delay = (task.t() - Utc::now()).num_milliseconds() as f32 / 1000.0;
92            if task_delay.abs() > 2.0 {
93                log!("Task {tasks} delayed by {task_delay}s!");
94            }
95            let context_clone = Arc::clone(&context);
96            match self.exec_task(context_clone, task).await {
97                ExecExitSignal::Continue => {}
98                ExecExitSignal::SafeEvent => {
99                    return self.safe_handler(context_local).await;
100                }
101                ExecExitSignal::NewZOEvent(_) => {
102                    fatal!("Unexpected task exit signal!");
103                }
104            };
105            tasks += 1;
106        }
107        OpExitSignal::Continue
108    }
109
110    /// Waits until a task’s due time or handles early exit signals while executing a wait primitive.
111    ///
112    /// # Arguments
113    /// * `context` - Shared reference to the mode context.
114    /// * `due` - Scheduled task start time.
115    ///
116    /// # Returns
117    /// * `WaitExitSignal` - Resulting signal from monitoring exit conditions.
118    async fn exec_task_wait(&self, context: Arc<ModeContext>, due: DateTime<Utc>)
119    -> WaitExitSignal;
120    
121    /// Executes a single task and returns a signal indicating whether to continue or exit.
122    ///
123    /// # Arguments
124    /// * `context` - Shared reference to the mode context.
125    /// * `task` - Task to be executed.
126    ///
127    /// # Returns
128    /// * `ExecExitSignal` - Resulting signal after task execution.
129    async fn exec_task(&self, context: Arc<ModeContext>, task: Task) -> ExecExitSignal;
130
131    /// Handles unplanned safe mode transition.
132    ///
133    /// # Arguments
134    /// * `context` - Shared reference to the mode context.
135    ///
136    /// # Returns
137    /// * `OpExitSignal` - Signal after executing safe-mode exit logic.
138    async fn safe_handler(&self, context: Arc<ModeContext>) -> OpExitSignal;
139
140    /// Handles the reception of a new Zoned Objective (ZO).
141    ///
142    /// # Arguments
143    /// * `context` - Shared reference to the mode context.
144    /// * `obj` - The new `KnownImgObjective` that triggered the transition.
145    ///
146    /// # Returns
147    /// * `OptOpExitSignal` - Optional signal indicating a mode switch or continuation.
148    async fn zo_handler(
149        &self,
150        context: &Arc<ModeContext>,
151        obj: KnownImgObjective,
152    ) -> OptOpExitSignal;
153    
154    /// Handles beacon-related events (e.g., mode changes or new signals).
155    ///
156    /// # Arguments
157    /// * `context` - Shared reference to the mode context.
158    ///
159    /// # Returns
160    /// * `OptOpExitSignal` - Optional signal indicating a mode switch or continuation.
161    async fn bo_event_handler(&self, context: &Arc<ModeContext>) -> OptOpExitSignal;
162
163    /// Handles cleanup and transition logic when exiting a mode.
164    ///
165    /// # Arguments
166    /// * `context` - Shared reference to the mode context.
167    ///
168    /// # Returns
169    /// * `Box<dyn GlobalMode>` - The next mode to transition into.
170    async fn exit_mode(&self, context: Arc<ModeContext>) -> Box<dyn GlobalMode>;
171}
172
173/// An internal extension trait for [`GlobalMode`] that encapsulates logic specific to
174/// time-constrained orbital task execution.
175///
176/// The [`OrbitalMode`] trait provides utility methods for task waiting, event monitoring,
177/// and logging transitions related to orbital activities, such as beacon detection or
178/// zoned objective events.
179///
180/// This trait is not intended to be used directly outside the mode control subsystem.
181pub(super) trait OrbitalMode: GlobalMode {
182    /// Returns the maximum duration allowed for scheduled task waiting.
183    /// If the remaining time until a task is due exceeds this value, the mode may begin
184    /// long-wait procedures; otherwise, a fallback short sleep is used.
185    ///
186    /// # Returns
187    /// * [`TimeDelta`] – Maximum duration (default: 10 seconds).
188    fn get_max_dt() -> TimeDelta { TimeDelta::seconds(10) }
189
190    /// Returns a reference to the [`BaseMode`] associated with this orbital mode.
191    ///
192    /// This is used for delegating tasks and behavior such as wait and scheduling primitives.
193    ///
194    /// # Returns
195    /// * `&BaseMode` – The current mode (e.g., Mapping or Beacon Objective Scanning).
196    fn base(&self) -> &BaseMode;
197
198    /// Waits until a scheduled task’s due time or returns early if a notable event occurs.
199    ///
200    /// While waiting, this method concurrently monitors for:
201    /// - Safe mode triggers
202    /// - New zoned objectives (ZO)
203    /// - Beacon state changes (BO)
204    ///
205    /// It also supports short or long sleep strategies depending on how far the task lies in the future.
206    ///
207    /// # Arguments
208    /// * `context` – Shared reference to the current [`ModeContext`].
209    /// * `due` – Scheduled time at which the next task is expected to start.
210    ///
211    /// # Returns
212    /// * [`WaitExitSignal`] – An event indicating why the wait ended (safe event, ZO, or BO, ...).
213    async fn exec_task_wait(
214        &self,
215        context: Arc<ModeContext>,
216        due: DateTime<Utc>,
217    ) -> WaitExitSignal {
218        let safe_mon = context.super_v().safe_mon();
219        let mut zo_mon = context.zo_mon().write().await;
220        let bo_mon = context.bo_mon();
221        let cancel_task = CancellationToken::new();
222
223        let fut: Pin<Box<dyn Future<Output = Result<_, JoinError>> + Send>> =
224            if (due - Utc::now()) > Self::get_max_dt() {
225                Box::pin(self.base().get_wait(Arc::clone(&context), due, cancel_task.clone()).await)
226            } else {
227                warn!("Task wait time too short. Just waiting!");
228                Box::pin(async {
229                    let sleep = (due - Utc::now()).to_std().unwrap_or(DT_0_STD);
230                    tokio::time::timeout(sleep, cancel_task.cancelled()).await.ok().unwrap_or(());
231                    Ok(())
232                })
233            };
234        let bo_change_signal = self.base().get_rel_bo_event();
235        tokio::pin!(fut);
236        tokio::select! {
237            exit_sig = &mut fut => {
238                exit_sig.unwrap_or_else(|_|fatal!("Task wait hung up!"));
239                WaitExitSignal::Continue
240            },
241            () = safe_mon.notified() => {
242                cancel_task.cancel();
243                fut.await.ok();
244                WaitExitSignal::SafeEvent
245            },
246            msg =  zo_mon.recv() => {
247                let img_obj = msg.unwrap_or_else(||fatal!("Objective monitor wait hung up!"));
248                cancel_task.cancel();
249                fut.await.ok();
250                WaitExitSignal::NewZOEvent(img_obj)
251            }
252            () = Self::monitor_bo_mon_change(bo_change_signal, bo_mon) => {
253                cancel_task.cancel();
254                fut.await.ok();
255                WaitExitSignal::BOEvent
256            }
257
258        }
259    }
260
261    /// Continuously monitors the [`BeaconControllerState`] until it changes to the expected value.
262    ///
263    /// Used to react to asynchronous events in which the beacon scanning mode (e.g., active or inactive)
264    /// must trigger a response in the scheduler or mode transition logic.
265    ///
266    /// # Arguments
267    /// * `sig` – The [`BeaconControllerState`] state to wait for.
268    /// * `bo_mon` – A watch receiver providing asynchronous access to beacon state changes.
269    async fn monitor_bo_mon_change(
270        sig: BeaconControllerState,
271        bo_mon: &RwLock<Receiver<BeaconControllerState>>,
272    ) {
273        let mut bo_mon_lock = bo_mon.write().await;
274        loop {
275            if let Ok(()) = bo_mon_lock.changed().await {
276                let sent_sig = bo_mon_lock.borrow_and_update();
277                let sent_sig_ref = &*sent_sig;
278                if discriminant(&sig) == discriminant(sent_sig_ref) {
279                    return;
280                }
281            }
282        }
283    }
284
285    /// Logs a beacon-related event and finalizes the orbit at the current satellite position.
286    ///
287    /// This is used to capture the reason for switching out of the current [`BaseMode`],
288    /// typically due to either beacon objective completion or expiry.
289    ///
290    /// # Arguments
291    /// * `context` – Shared reference to the current [`ModeContext`].
292    /// * `base` – The [`BaseMode`] that determines the rationale for finishing the orbit segment.
293    async fn log_bo_event(&self, context: &Arc<ModeContext>, base: BaseMode) {
294        match base {
295            BaseMode::BeaconObjectiveScanningMode => context.o_ch_lock().write().await.finish(
296                context.k().f_cont().read().await.current_pos(),
297                self.new_bo_rationale(),
298            ),
299            BaseMode::MappingMode => context.o_ch_lock().write().await.finish(
300                context.k().f_cont().read().await.current_pos(),
301                self.bo_done_rationale(),
302            ),
303        }
304    }
305}