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}