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