1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//! Debug infrastructure types for the rill-lang execution engine.
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use rill_core::queues::spsc::SpscQueue;
/// A single frame of signal data captured at a probe point.
#[derive(Debug, Clone, Copy, Default)]
pub struct ProbeFrame {
/// Raw bits of the captured signal value.
pub value_bits: u64,
/// Block index when this frame was captured.
pub block_index: u64,
}
/// A fixed-size, Copy-compatible string buffer.
#[derive(Debug, Clone, Copy)]
pub struct CmdStr<const N: usize> {
bytes: [u8; N],
len: u8,
}
impl<const N: usize> CmdStr<N> {
/// Create a new CmdStr from a string slice, truncating to N bytes.
pub fn new(s: &str) -> Self {
let mut bytes = [0u8; N];
let len = s.len().min(N);
bytes[..len].copy_from_slice(&s.as_bytes()[..len]);
Self {
bytes,
len: len as u8,
}
}
/// Return the contained string slice.
pub fn as_str(&self) -> &str {
let len = self.len as usize;
std::str::from_utf8(&self.bytes[..len]).unwrap_or("")
}
/// Returns true if the string is empty.
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
impl<const N: usize> Default for CmdStr<N> {
fn default() -> Self {
Self {
bytes: [0u8; N],
len: 0,
}
}
}
/// A single frame of command data captured from the actor mailbox.
#[derive(Debug, Clone, Copy, Default)]
pub struct CommandFrame {
/// Block index when the command was received.
pub block_index: u64,
/// Short label identifying the command kind.
pub command_kind: CmdStr<32>,
/// Name of the target graph node.
pub node_name: CmdStr<64>,
/// Name of the parameter being set.
pub param_name: CmdStr<64>,
/// String representation of the parameter value.
pub value_repr: CmdStr<128>,
}
/// Per-probe runtime slot stored in the engine.
pub struct ProbeSlot {
/// Whether this probe is actively capturing data.
pub enabled: AtomicBool,
/// When set, the engine pauses on value capture.
pub break_flag: AtomicBool,
/// Indicates the engine is currently paused at this breakpoint.
pub paused_flag: AtomicBool,
/// Most recently captured value, for poll-based inspection.
pub last_value: AtomicU64,
/// Ring buffer of captured probe frames.
pub queue: Arc<SpscQueue<ProbeFrame, 64>>,
}
impl ProbeSlot {
/// Create a new disabled probe slot.
pub fn new() -> Self {
Self {
enabled: AtomicBool::new(false),
break_flag: AtomicBool::new(false),
paused_flag: AtomicBool::new(false),
last_value: AtomicU64::new(0),
queue: Arc::new(SpscQueue::new()),
}
}
/// Returns true if the probe is enabled and capturing.
#[inline]
pub fn is_active(&self) -> bool {
self.enabled.load(Ordering::Acquire)
}
/// Returns true if the probe is enabled and has a breakpoint set.
#[inline]
pub fn is_breakpoint(&self) -> bool {
self.enabled.load(Ordering::Acquire) && self.break_flag.load(Ordering::Acquire)
}
}
impl Default for ProbeSlot {
fn default() -> Self {
Self::new()
}
}
/// Debug control atomics shared between engine and collector/debugger threads.
#[derive(Clone)]
pub struct DebugControl {
/// When true, the engine spins waiting for resume.
pub global_pause: Arc<AtomicBool>,
/// When true, the engine resumes execution.
pub global_resume: Arc<AtomicBool>,
/// Monotonic counter incremented each processing block.
pub block_index: Arc<AtomicU64>,
}
impl DebugControl {
/// Create a new DebugControl with all flags in their default state.
pub fn new() -> Self {
Self {
global_pause: Arc::new(AtomicBool::new(false)),
global_resume: Arc::new(AtomicBool::new(false)),
block_index: Arc::new(AtomicU64::new(0)),
}
}
/// Release a paused engine and signal it to continue.
pub fn cont(&self) {
self.global_pause.store(false, Ordering::Release);
self.global_resume.store(true, Ordering::Release);
}
/// Step one tick: allow engine to process one block, then re-pause.
pub fn step(&self) {
self.global_resume.store(true, Ordering::Release);
}
/// Signal the engine to pause at the next inter-tick boundary.
pub fn pause(&self) {
self.global_pause.store(true, Ordering::Release);
self.global_resume.store(false, Ordering::Release);
}
}
impl Default for DebugControl {
fn default() -> Self {
Self::new()
}
}