Skip to main content

forge_audio/
rt_safety.rs

1//! Real-time safety enforcement.
2//!
3//! Provides utilities for verifying the audio callback is allocation-free.
4//! The actual allocator wrapping must happen at the binary level (main.rs),
5//! not at the library level. This module provides the API and documentation.
6
7/// Check if a block of code performs any heap allocations.
8///
9/// This uses a simple boolean flag approach that works without replacing
10/// the global allocator. The audio thread sets this flag, and any code
11/// can check it to decide whether allocation is safe.
12///
13/// For full enforcement, the binary (forge-app-daw) should use
14/// assert_no_alloc's allocator wrapper.
15use std::sync::atomic::{AtomicBool, Ordering};
16
17/// Global flag: are we inside a real-time context?
18static IN_RT_CONTEXT: AtomicBool = AtomicBool::new(false);
19
20/// Enter real-time context. Call at the start of the audio callback.
21pub fn enter_rt() {
22    IN_RT_CONTEXT.store(true, Ordering::Release);
23}
24
25/// Exit real-time context. Call at the end of the audio callback.
26pub fn exit_rt() {
27    IN_RT_CONTEXT.store(false, Ordering::Release);
28}
29
30/// Check if we're currently in a real-time context.
31pub fn is_rt() -> bool {
32    IN_RT_CONTEXT.load(Ordering::Acquire)
33}
34
35/// Run a closure in real-time context. Sets the RT flag, runs the closure,
36/// then clears the flag.
37///
38/// In the audio callback:
39/// ```ignore
40/// rt_guard(|| {
41///     engine.process(frames);
42/// });
43/// ```
44pub fn rt_guard<R>(f: impl FnOnce() -> R) -> R {
45    enter_rt();
46    let result = f();
47    exit_rt();
48    result
49}
50
51/// Marker trait for types that are safe to use in the audio callback.
52/// Types that implement this promise they will never allocate, block, or do I/O.
53pub trait RtSafe: Send {}
54
55impl RtSafe for f32 {}
56impl RtSafe for f64 {}
57impl RtSafe for i32 {}
58impl RtSafe for u32 {}
59impl RtSafe for usize {}
60impl RtSafe for bool {}
61
62/// A list of real-time rules for documentation and enforcement.
63pub const RT_RULES: &[&str] = &[
64    "No Mutex, RwLock, or any blocking synchronization",
65    "No Box::new, Vec::push, String::from, or any heap allocation",
66    "No disk reads, network calls, or system timers",
67    "No Drop of heap objects (use basedrop for deferred reclamation)",
68    "All memory must be pre-allocated before entering the callback",
69];
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn test_rt_flag_default_off() {
77        // Reset state for test isolation.
78        exit_rt();
79        assert!(!is_rt());
80    }
81
82    #[test]
83    fn test_rt_guard_sets_flag() {
84        exit_rt(); // Reset.
85        let was_rt = rt_guard(|| {
86            is_rt()
87        });
88        assert!(was_rt, "Should be in RT context inside guard");
89        assert!(!is_rt(), "Should exit RT context after guard");
90    }
91
92    #[test]
93    fn test_enter_exit_manual() {
94        exit_rt();
95        assert!(!is_rt());
96        enter_rt();
97        assert!(is_rt());
98        exit_rt();
99        assert!(!is_rt());
100    }
101
102    #[test]
103    fn test_rt_rules_not_empty() {
104        assert!(!RT_RULES.is_empty());
105        assert!(RT_RULES.len() >= 4);
106    }
107
108    #[test]
109    fn test_nested_rt_guard() {
110        exit_rt();
111        rt_guard(|| {
112            assert!(is_rt());
113            // Simulating nested call — still in RT context.
114            let inner = is_rt();
115            assert!(inner);
116        });
117        assert!(!is_rt());
118    }
119}