Skip to main content

laminar_core/alloc/
guard.rs

1//! RAII guard for hot path sections.
2//!
3//! Provides automatic enable/disable of allocation detection.
4
5use std::marker::PhantomData;
6
7#[cfg(feature = "allocation-tracking")]
8use super::detector;
9
10/// RAII guard for hot path sections.
11///
12/// When created, enables allocation detection for the current thread.
13/// When dropped, disables allocation detection.
14///
15/// # Example
16///
17/// ```rust,ignore
18/// use laminar_core::alloc::HotPathGuard;
19///
20/// fn process_event(event: &Event) {
21///     // Any allocation after this will panic (with allocation-tracking feature)
22///     let _guard = HotPathGuard::enter("process_event");
23///
24///     // Do hot path processing...
25///     // Vec::new() here would panic!
26///
27/// } // Guard dropped here, allocation detection disabled
28/// ```
29///
30/// # Zero-Cost in Release Builds
31///
32/// Without the `allocation-tracking` feature, this guard compiles to a no-op.
33/// The enter/drop methods are inlined to nothing.
34pub struct HotPathGuard {
35    /// Section name for error messages
36    #[cfg(feature = "allocation-tracking")]
37    section: &'static str,
38
39    /// Marker to make the guard !Send and !Sync
40    /// Hot path detection is thread-local, so guards shouldn't be moved between threads
41    _marker: PhantomData<*const ()>,
42}
43
44impl HotPathGuard {
45    /// Enter a hot path section.
46    ///
47    /// After calling this, any heap allocation on the current thread will panic
48    /// (when `allocation-tracking` feature is enabled).
49    ///
50    /// # Arguments
51    ///
52    /// * `section` - Name of the hot path section (used in error messages)
53    ///
54    /// # Returns
55    ///
56    /// A guard that will disable detection when dropped.
57    ///
58    /// # Example
59    ///
60    /// ```rust,ignore
61    /// let _guard = HotPathGuard::enter("reactor::poll");
62    /// // Hot path code here
63    /// ```
64    #[inline]
65    #[must_use]
66    pub fn enter(#[allow(unused_variables)] section: &'static str) -> Self {
67        #[cfg(feature = "allocation-tracking")]
68        detector::enable_hot_path(section);
69
70        Self {
71            #[cfg(feature = "allocation-tracking")]
72            section,
73            _marker: PhantomData,
74        }
75    }
76
77    /// Check if currently in a hot path section.
78    #[inline]
79    #[must_use]
80    pub fn is_active() -> bool {
81        #[cfg(feature = "allocation-tracking")]
82        {
83            detector::is_hot_path_enabled()
84        }
85        #[cfg(not(feature = "allocation-tracking"))]
86        {
87            false
88        }
89    }
90
91    /// Get the current section name, if in hot path.
92    #[inline]
93    #[must_use]
94    #[cfg(feature = "allocation-tracking")]
95    pub fn current_section(&self) -> &'static str {
96        self.section
97    }
98}
99
100impl Drop for HotPathGuard {
101    #[inline]
102    fn drop(&mut self) {
103        #[cfg(feature = "allocation-tracking")]
104        detector::disable_hot_path();
105    }
106}
107
108// Implement Debug for better error messages
109impl std::fmt::Debug for HotPathGuard {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        #[cfg(feature = "allocation-tracking")]
112        {
113            f.debug_struct("HotPathGuard")
114                .field("section", &self.section)
115                .finish()
116        }
117        #[cfg(not(feature = "allocation-tracking"))]
118        {
119            f.debug_struct("HotPathGuard").finish()
120        }
121    }
122}
123
124/// Macro to mark a function as hot path.
125///
126/// This is a convenience macro that creates a `HotPathGuard` at the start
127/// of the function with the function name as the section.
128///
129/// # Example
130///
131/// ```rust,ignore
132/// use laminar_core::hot_path;
133///
134/// fn process_event(event: &Event) {
135///     hot_path!();
136///     // Hot path code...
137/// }
138///
139/// // Or with a custom section name:
140/// fn custom_section() {
141///     hot_path!("custom::section::name");
142///     // Hot path code...
143/// }
144/// ```
145#[macro_export]
146macro_rules! hot_path {
147    () => {
148        let _hot_path_guard =
149            $crate::alloc::HotPathGuard::enter(concat!(module_path!(), "::", stringify!(fn)));
150    };
151    ($section:expr) => {
152        let _hot_path_guard = $crate::alloc::HotPathGuard::enter($section);
153    };
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn test_guard_enter_exit() {
162        // Initially not in hot path
163        assert!(!HotPathGuard::is_active());
164
165        {
166            let _guard = HotPathGuard::enter("test");
167
168            #[cfg(feature = "allocation-tracking")]
169            assert!(HotPathGuard::is_active());
170        }
171
172        // After guard dropped
173        assert!(!HotPathGuard::is_active());
174    }
175
176    #[test]
177    fn test_guard_debug() {
178        let guard = HotPathGuard::enter("test_section");
179        let debug_str = format!("{guard:?}");
180        assert!(debug_str.contains("HotPathGuard"));
181    }
182
183    #[test]
184    fn test_nested_guards() {
185        let _outer = HotPathGuard::enter("outer");
186
187        #[cfg(feature = "allocation-tracking")]
188        assert!(HotPathGuard::is_active());
189
190        {
191            let _inner = HotPathGuard::enter("inner");
192
193            #[cfg(feature = "allocation-tracking")]
194            assert!(HotPathGuard::is_active());
195        }
196
197        // Outer guard still active after inner dropped
198        // Note: This is expected behavior - the outer guard keeps detection enabled
199        #[cfg(feature = "allocation-tracking")]
200        assert!(HotPathGuard::is_active());
201    }
202}