gdnative_core/
profiler.rs

1//! Interface to Godot's built-in profiler.
2
3use std::borrow::Cow;
4use std::ffi::{CStr, CString};
5use std::time::{Duration, Instant};
6
7use crate::private::try_get_api;
8
9/// A string encoding information about the code being profiled for Godot's built-in profiler.
10///
11/// The string should be in the form of `{file}::{line_number}::{tag}`, where `tag` is an
12/// identifier of the code, usually be the name of the method. None of the substrings should
13/// contain `::`.
14///
15/// To create a `Signature` in the correct form, see [`Signature::new()`] or [`profile_sig!`]. To
16/// create a `Signature` from an existing `CStr` or `CString`, see [`Signature::from_raw()`] and
17/// [`Signature::from_raw_owned()`].
18#[derive(Clone, Eq, PartialEq, Hash, Debug)]
19pub struct Signature<'a> {
20    sig: Cow<'a, CStr>,
21}
22
23impl<'a> Signature<'a> {
24    /// Creates a `Signature` from a `CStr` in the specified format. The format is not
25    /// checked.
26    ///
27    /// Adding profiling data using an invalid `Signature` may cause incorrect information to
28    /// show up in the editor.
29    #[inline(always)]
30    pub const fn from_raw(sig: &'a CStr) -> Self {
31        Signature {
32            sig: Cow::Borrowed(sig),
33        }
34    }
35
36    /// Creates a `Signature` from a NUL-terminated byte slice, containing a string in the
37    /// specified format. Neither the format nor whether the slice is correctly NUL-terminated
38    /// is checked.
39    ///
40    /// This is a convenience method for `Signature::from_raw(CStr::from_bytes_with_nul_unchecked(bytes))`.
41    ///
42    /// Adding profiling data using an invalid `Signature` may cause incorrect information to
43    /// show up in the editor.
44    ///
45    /// # Safety
46    ///
47    /// This function will cast the provided `bytes` to a `CStr` wrapper without performing any
48    /// sanity checks. The provided slice **must** be nul-terminated and not contain any
49    /// interior nul bytes.
50    #[inline(always)]
51    pub unsafe fn from_bytes_with_nul_unchecked(bytes: &'a [u8]) -> Self {
52        let sig = CStr::from_bytes_with_nul_unchecked(bytes);
53        Self::from_raw(sig)
54    }
55
56    /// Create a borrowed version of `self` for repeated use with [`add_data()`][Self::add_data()] or [`profile()`][Self::add_data()].
57    #[inline(always)]
58    pub fn borrow(&self) -> Signature<'_> {
59        Signature {
60            sig: Cow::Borrowed(&*self.sig),
61        }
62    }
63
64    /// Add a data point to Godot's built-in profiler using this signature.
65    ///
66    /// See the free function [`profiler::add_data()`][add_data()].
67    #[inline]
68    pub fn add_data(&self, time: Duration) {
69        add_data(self.borrow(), time)
70    }
71
72    /// Times a closure and adds the measured time to Godot's built-in profiler with this
73    /// signature, and then returns it's return value.
74    ///
75    /// See the free function [`profiler::profile()`][profile()].
76    #[inline]
77    pub fn profile<F, R>(&self, f: F) -> R
78    where
79        F: FnOnce() -> R,
80    {
81        profile(self.borrow(), f)
82    }
83
84    fn as_ptr(&self) -> *const libc::c_char {
85        self.sig.as_ptr()
86    }
87}
88
89impl Signature<'static> {
90    /// Creates a `Signature` in the correct form using the given variables. The format is
91    /// checked at runtime.
92    ///
93    /// # Panics
94    ///
95    /// If `file` or `tag` contain `::` or NUL-bytes.
96    #[inline]
97    pub fn new(file: &str, line: u32, tag: &str) -> Self {
98        if file.contains("::") {
99            panic!("file name should not contain `::`");
100        }
101
102        if tag.contains("::") {
103            panic!("tag should not contain `::`");
104        }
105
106        let sig = CString::new(format!("{file}::{line}::{tag}"))
107            .expect("file and tag should not contain NUL bytes");
108        Self::from_raw_owned(sig)
109    }
110
111    /// Creates a `Signature` from an owned `CString` in the specified format. The format is not
112    /// checked.
113    ///
114    /// Adding profiling data using an invalid `Signature` may cause incorrect information to
115    /// show up in the editor.
116    #[inline(always)]
117    pub const fn from_raw_owned(sig: CString) -> Self {
118        Signature {
119            sig: Cow::Owned(sig),
120        }
121    }
122}
123
124/// Add a data point to Godot's built-in profiler. The profiler only has microsecond precision.
125/// Sub-microsecond time is truncated.
126///
127/// If the GDNative API is not initialized at the point when this is called, the function will
128/// fail silently.
129///
130/// # Panics
131///
132/// If the number of microseconds in `time` exceeds the range of `u64`.
133#[inline]
134pub fn add_data(signature: Signature<'_>, time: Duration) {
135    if let Some(api) = try_get_api() {
136        let time_in_usec = u64::try_from(time.as_micros())
137            .expect("microseconds in `time` should not exceed the range of u64");
138
139        unsafe {
140            (api.godot_nativescript_profiling_add_data)(signature.as_ptr(), time_in_usec);
141        }
142    }
143}
144
145/// Times a closure and adds the measured time to Godot's built-in profiler with the given
146/// signature, and then returns it's return value.
147#[inline]
148pub fn profile<F, R>(signature: Signature<'_>, f: F) -> R
149where
150    F: FnOnce() -> R,
151{
152    let start = Instant::now();
153    let ret = f();
154    add_data(signature, Instant::now() - start);
155    ret
156}
157
158/// Convenience macro to create a profiling signature with a given tag.
159///
160/// The expanded code will panic at runtime if the file name or `tag` contains `::` or
161/// any NUL-bytes.
162///
163/// See [`Signature`] for more information.
164///
165/// # Examples
166///
167/// ```rust
168/// # fn main() {
169/// use gdnative::profiler::{profile, profile_sig};
170///
171/// let answer = profile(profile_sig!("foo"), || 42);
172/// assert_eq!(answer, 42);
173/// # }
174/// ```
175#[macro_export]
176macro_rules! _profile_sig {
177    ($tag:expr) => {
178        $crate::profiler::Signature::new(file!(), line!(), $tag)
179    };
180}
181
182// Export macro in this module
183pub use _profile_sig as profile_sig;