Skip to main content

ranvier_core/
debug.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashSet;
3use std::sync::{Arc, Mutex};
4use tokio::sync::Notify;
5
6/// Current execution state of the debugger.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8pub enum DebugState {
9    /// Execution is proceeding normally.
10    Running,
11    /// Execution is currently paused at a node.
12    Paused,
13}
14
15/// A controller used to manage breakpoints and execution flow for a running Axon.
16///
17/// This is typically stored in the `Bus` to allow the Axon runtime to check
18/// for pause-points between node executions.
19#[derive(Clone)]
20pub struct DebugControl {
21    inner: Arc<DebugControlInner>,
22}
23
24struct DebugControlInner {
25    breakpoints: Mutex<HashSet<String>>,
26    pause_next: Mutex<bool>,
27    notify: Notify,
28    state: Mutex<DebugState>,
29}
30
31impl DebugControl {
32    /// Create a new DebugControl in the 'Running' state with no breakpoints.
33    pub fn new() -> Self {
34        Self {
35            inner: Arc::new(DebugControlInner {
36                breakpoints: Mutex::new(HashSet::new()),
37                pause_next: Mutex::new(false),
38                notify: Notify::new(),
39                state: Mutex::new(DebugState::Running),
40            }),
41        }
42    }
43
44    /// Add a breakpoint for a specific node ID.
45    pub fn set_breakpoint(&self, node_id: String) {
46        self.inner.breakpoints.lock().expect("debug mutex poisoned").insert(node_id);
47    }
48
49    /// Remove a breakpoint for a specific node ID.
50    pub fn remove_breakpoint(&self, node_id: &str) {
51        self.inner.breakpoints.lock().expect("debug mutex poisoned").remove(node_id);
52    }
53
54    /// Request execution to pause at the next available node.
55    pub fn pause(&self) {
56        *self.inner.pause_next.lock().expect("debug mutex poisoned") = true;
57    }
58
59    /// Resume execution from a paused state.
60    pub fn resume(&self) {
61        *self.inner.pause_next.lock().expect("debug mutex poisoned") = false;
62        *self.inner.state.lock().expect("debug mutex poisoned") = DebugState::Running;
63        self.inner.notify.notify_waiters();
64    }
65
66    /// Resume execution but pause again at the very next node.
67    pub fn step(&self) {
68        *self.inner.pause_next.lock().expect("debug mutex poisoned") = true;
69        *self.inner.state.lock().expect("debug mutex poisoned") = DebugState::Running;
70        self.inner.notify.notify_waiters();
71    }
72
73    /// Check if the current node should trigger a pause.
74    ///
75    /// This consumes the internal "pause_next" flag if it was set.
76    pub fn should_pause(&self, node_id: &str) -> bool {
77        let breakpoints = self.inner.breakpoints.lock().expect("debug mutex poisoned");
78        let mut pause_next = self.inner.pause_next.lock().expect("debug mutex poisoned");
79        let hit_breakpoint = breakpoints.contains(node_id);
80        let pause_requested = *pause_next;
81
82        if hit_breakpoint || pause_requested {
83            *pause_next = false; // Consume the "step" or "pause" request
84            true
85        } else {
86            false
87        }
88    }
89
90    /// Explicitly transition to Paused state and wait for a resume signal.
91    pub async fn wait(&self) {
92        *self.inner.state.lock().expect("debug mutex poisoned") = DebugState::Paused;
93        // Wait for resume() or step() to notify
94        self.inner.notify.notified().await;
95    }
96
97    /// Check if the current node requires a pause and wait if so.
98    ///
99    /// Deprecated in favor of manual should_pause + wait for better event timing.
100    pub async fn wait_if_needed(&self, node_id: &str) {
101        if self.should_pause(node_id) {
102            self.wait().await;
103        }
104    }
105
106    /// Get the current debugger state.
107    pub fn state(&self) -> DebugState {
108        *self.inner.state.lock().expect("debug mutex poisoned")
109    }
110
111    /// List all currently set breakpoint node IDs.
112    pub fn list_breakpoints(&self) -> Vec<String> {
113        self.inner
114            .breakpoints
115            .lock()
116            .unwrap()
117            .iter()
118            .cloned()
119            .collect()
120    }
121}
122
123impl Default for DebugControl {
124    fn default() -> Self {
125        Self::new()
126    }
127}