Skip to main content

libobs_wrapper/
macros.rs

1#[macro_export]
2macro_rules! run_with_obs_impl {
3    ($runtime:expr, $operation:expr) => {
4        $crate::run_with_obs_impl!($runtime, (), $operation)
5    };
6    ($runtime:expr, ($($var:ident),* $(,)*), $operation:expr) => {{
7        $(let $var = $var.clone();)*
8        $runtime.run_with_obs_result(move || {
9            $(let $var = $var;)*
10            let inner_obs_run = { $operation };
11            inner_obs_run()
12        })
13    }};
14}
15
16/// Execute work on the OBS actor while preserving the runtime's structured error.
17#[macro_export]
18macro_rules! run_with_obs {
19    ($runtime:expr, $operation:expr) => {{
20        $crate::run_with_obs_impl!($runtime, $operation)
21    }};
22    ($runtime:expr, ($($var:ident),* $(,)*), $operation:expr) => {{
23        $crate::run_with_obs_impl!($runtime, ($($var),*), $operation)
24    }};
25}
26
27/// Implement a non-panicking native cleanup guard.
28///
29/// Cleanup is enqueued on the runtime's dedicated cleanup queue, so destructors do
30/// not synchronously execute libobs calls or require a Tokio runtime.
31#[macro_export]
32macro_rules! impl_obs_drop {
33    ($struct_name: ident, $operation:expr) => {
34        $crate::impl_obs_drop!($struct_name, (), $operation);
35    };
36    ($struct_name: ident, ($($var:ident),* $(,)*), $operation:expr) => {
37        impl Drop for $struct_name {
38            fn drop(&mut self) {
39                log::trace!("Dropping {}...", stringify!($struct_name));
40                $(let $var = self.$var.clone();)*
41                // Move one Send tuple rather than accessing wrapper fields across the
42                // closure boundary. This defeats precise field capture of raw pointers
43                // without introducing a generic "make anything Send" adapter.
44                let cleanup_payload = ($($var,)*);
45                let runtime = self.runtime.clone();
46                let cleanup_runtime = runtime.clone();
47                runtime.defer_obs_cleanup(move || {
48                    // Keep the payload wrapped until it crosses the actor-call boundary so
49                    // Rust's precise closure capture cannot extract a raw pointer field.
50                    let result = cleanup_runtime.run_with_obs_result(move || {
51                        let ($($var,)*) = cleanup_payload;
52                        let inner_obs_drop = { $operation };
53                        inner_obs_drop()
54                    });
55                    if let Err(err) = result {
56                        log::error!(
57                            "Failed to run native cleanup for {} on the OBS actor: {:?}",
58                            stringify!($struct_name),
59                            err
60                        );
61                    }
62                });
63            }
64        }
65    };
66}
67
68/// Implements PartialEq, Eq and Hash für a struct by comparing the inner pointer given by `as_ptr()`.
69macro_rules! impl_eq_of_ptr {
70    ($struct: ty) => {
71        impl PartialEq for $struct {
72            fn eq(&self, other: &Self) -> bool {
73                self.as_ptr().native_id() == other.as_ptr().native_id()
74            }
75        }
76
77        impl Eq for $struct {}
78
79        impl std::hash::Hash for $struct {
80            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
81                self.as_ptr().native_id().hash(state);
82            }
83        }
84    };
85}
86
87/// Implements PartialEq, Eq and Hash for a managed OBS object using its opaque ID.
88macro_rules! impl_eq_of_obs_object {
89    ($struct: ty) => {
90        impl PartialEq for $struct {
91            fn eq(&self, other: &Self) -> bool {
92                crate::data::object::ObsObjectTrait::object_id(self)
93                    == crate::data::object::ObsObjectTrait::object_id(other)
94            }
95        }
96
97        impl Eq for $struct {}
98
99        impl std::hash::Hash for $struct {
100            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
101                crate::data::object::ObsObjectTrait::object_id(self).hash(state);
102            }
103        }
104    };
105}
106
107#[cfg(windows)]
108macro_rules! enum_from_number {
109    ($var: ident, $numb: expr) => {{
110        use num_traits::FromPrimitive;
111        $var::from_i32($numb)
112    }};
113}
114
115#[cfg(not(windows))]
116macro_rules! enum_from_number {
117    ($var: ident, $numb: expr) => {{
118        use num_traits::FromPrimitive;
119        $var::from_u32($numb)
120    }};
121}
122
123/// Defines a trait that conditionally includes Send + Sync bounds when the enable_runtime feature is enabled.
124/// This avoids duplicating trait definitions for runtime vs non-runtime scenarios.
125///
126/// # Example
127/// ```ignore
128/// trait_with_optional_send_sync! {
129///     #[doc(hidden)]
130///     pub trait MyTrait: Debug {
131///         fn my_method(&self);
132///     }
133/// }
134/// ```
135/// This expands to two trait definitions:
136/// - With enable_runtime: `pub trait MyTrait: Debug + Send + Sync { ... }`
137/// - Without enable_runtime: `pub trait MyTrait: Debug { ... }`
138macro_rules! trait_with_optional_send_sync {
139    (
140        $(#[$meta:meta])*
141        $vis:vis trait $trait_name:ident: $base_bound:path {
142            $($body:tt)*
143        }
144    ) => {
145        #[cfg(feature="enable_runtime")]
146        $(#[$meta])*
147        $vis trait $trait_name: $base_bound + Send + Sync {
148            $($body)*
149        }
150
151        #[cfg(not(feature="enable_runtime"))]
152        $(#[$meta])*
153        $vis trait $trait_name: $base_bound {
154            $($body)*
155        }
156    };
157}
158
159pub(crate) use enum_from_number;
160pub(crate) use impl_eq_of_obs_object;
161pub(crate) use impl_eq_of_ptr;
162pub(crate) use trait_with_optional_send_sync;