tracy_gizmos/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg), deny(rustdoc::broken_intra_doc_links))]
2#![cfg_attr(any(doc, feature = "enabled"), deny(missing_docs))]
3#![cfg_attr(not(feature = "enabled"), allow(unused_variables))]
4#![cfg_attr(
5	feature = "unstable-function-names",
6	allow(incomplete_features),
7	feature(const_type_name),
8	feature(generic_const_exprs),
9)]
10
11//! Bindings for the client library of the
12//! [Tracy](https://github.com/wolfpld/tracy) profiler.
13//!
14//! Refer to the Tracy manual for details and profiler server usage.
15//!
16//! # How to use
17//!
18//! Add `tracy-gizmos` to your `Cargo.toml`:
19//!
20//! ```toml
21//! [dependencies]
22//! tracy-gizmos = { version = "0.0.1", features = ["enabled"] }
23//! ```
24//!
25//! Note that instrumentation is *disabled* by default.
26//!
27//! The usage is pretty straight-forward (for more details read the docs):
28//!
29//! ```no_run
30//! # fn work() { todo!() }
31//! fn main() {
32//!     let tracy = tracy_gizmos::start_capture();
33//!     tracy_gizmos::zone!("main");
34//!     work();
35//! }
36//! ```
37//!
38//! The [`#[instrument]`][instrument] attribute provies an easy way to
39//! add Tracy zones to functions. A function annotated with
40//! `#[instrument]` will create and enter a zone with that function's
41//! name everytime the function is called.
42//!
43//! For example:
44//!
45//! ```ignore
46//! # // this doctest is ignored, because it is impossible
47//! # // to run it with cfg(feature = "attributes").
48//! #[tracy_gizmos::instrument]
49//! fn work() {
50//!     // do stuff
51//! }
52//! ```
53//!
54//! You can find more examples showing how to use this crate
55//! [here][examples].
56//!
57//! [examples]: https://github.com/den-mentiei/tracy-gizmos/tree/main/examples
58//!
59//! # Features
60//!
61//! - **`enabled`** - enables the instrumentation and everything
62//! related to it.
63//! - **`attributes`** - includes support for the `#[instrument]` attribute.
64//! - **`unstable-function-names`** *(nightly only)* -
65//! includes the enclosing function name into every zone without
66//! additional runtime overhead.
67//!
68//! # Tracy features
69//!
70//! Tracy client functionality can be controlled pretty granularly.
71//! Refer to the Tracy's manual for more details. A corresponding
72//! define is listed for each feature, which can be used to search the
73//! Tracy's documentation or source code, if needed.
74//!
75//! The following features are available:
76//!
77//! - **`crash-handler`** - enables Tracy's crash handler, which
78//! intercepts application crashes and ensures the remaining profiling
79//! data is sent to the server together with a crash report details.
80//! Influences `TRACY_NO_CRASH_HANDLER`.
81//! - **`system-tracing`** - enables system-level tracing information
82//! collection (assuming that the profiled program was granted the
83//! priveleges needed, e.g. run as root or Administrator). Influences
84//! `TRACY_NO_SYSTEM_TRACING`.
85//! - **`context-switch`** - enables context switch information
86//! collection (assuming having the privelege, as above), which allows
87//! to see when a zone was actually executed or was waiting to be
88//! resumed. Influences `TRACY_NO_CONTEXT_SWITCH`.
89//! - **`sampling`** - enables the callstack sampling to augment
90//! instrumented data (requires privelege escalation on Windows).
91//! Influences `TRACY_NO_SAMPLING`.
92//! - **`callstack-inlines`** - enables the inline frames retrieval in
93//! callstacks, which provides more precise information but is
94//! magnitude slower. Influences `TRACY_NO_CALLSTACK_INLINES`.
95//! - **`hw-counters`** - enables the hardware performance counters
96//! sampling (available only on Linux or WSL): IPC, branch
97//! mispredicts, cache misses. Influences
98//! `TRACY_NO_SAMPLE_RETIREMENT`, `TRACY_NO_SAMPLE_BRANCH` and
99//! `TRACY_NO_SAMPLE_CACHE`.
100//! - **`code-transfer`** - enables the executable code retrieval,
101//! which captures parts of the application code for further analysis
102//! in Tracy. Be *extra careful* when working with non-public code!
103//! Influences `TRACY_NO_CODE_TRANSFER`.
104//! - **`vsync`** - enables the hardware Vsync events capture
105//! (assuming having the privilege), which will be reported as frame
106//! events per monitor. Influences `TRACY_NO_VSYNC_CAPTURE`.
107//! - **`no-exit`** - enables the short-lived application profiling
108//! improvement. When `TRACY_NO_EXIT` environment variable is set to
109//! `1`, profiled application will wait for the server connection to
110//! transfer the data, even if it has already finished executing.
111//! Influences `TRACY_NO_EXIT`.
112//! - **`broadcast`** - enables the local network announcement, so
113//! profiling servers can find the client. Influences
114//! `TRACY_NO_BROADCAST`.
115//! - **`only-localhost`** *(enabled by default)* - restricts Tracy to
116//! only listening on the localhost network interface. Influences
117//! `TRACY_ONLY_LOCALHOST`.
118//! - **`only-ipv4`** - restricts Tracy to only listenting on IPv4
119//! network interfaces. Influences `TRACY_ONLY_IPV4`.
120
121#[cfg(feature = "enabled")]
122use std::sync::atomic::{AtomicBool, Ordering};
123use std::marker::PhantomData;
124
125#[cfg_attr(docsrs, doc(cfg(feature = "attributes")))]
126#[doc(inline)]
127#[cfg(feature = "attributes")]
128pub use attrs::{instrument, capture};
129
130mod color;
131mod memory;
132mod plot;
133
134pub use color::*;
135pub use plot::*;
136
137/// Sets the current thread's name.
138///
139/// It is recommended to *always* use it in every thread, which uses
140/// Tracy capabilities.
141///
142/// If not set, Tracy will try to capture thread names through
143/// operating system data if context switch capture is active.
144/// However, this is only a fallback mechanism, and it shouldn't be
145/// relied upon.
146///
147/// # Examples
148///
149/// Ypu can specify a compile-time constant name for a particular
150/// thread, for example, a dedicated I/O thread:
151///
152/// ```no_run
153/// # use tracy_gizmos::*;
154/// # fn loop_and_do_io() {}
155/// std::thread::spawn(|| {
156///     set_thread_name!("I/O processor");
157///     zone!("I/O requests"); // Tracy will show it on this thread track.
158///     loop_and_do_io();
159/// });
160/// ```
161///
162/// You can make a name in runtime, for example, when you have an
163/// arbitrary amount of worker threads:
164///
165/// ```no_run
166/// # use tracy_gizmos::*;
167/// # fn get_next_worker_id() -> u32 { 0 }
168/// # fn loop_and_do_work() {}
169/// let id = get_next_worker_id();
170/// std::thread::spawn(move || {
171///     set_thread_name!("worker-thread {}", id);
172///     zone!("Working"); // Tracy will show it on this thread track.
173///     loop_and_do_work();
174/// });
175/// ```
176#[macro_export]
177#[cfg(any(doc, feature = "enabled"))]
178macro_rules! set_thread_name {
179	($name:literal) => {
180		// SAFETY: We null-terminate the string.
181		unsafe {
182			$crate::details::set_thread_name(concat!($name, '\0').as_ptr());
183		}
184	};
185
186	($format:literal, $($args:expr),*) => {{
187		let name = format!(concat!($format, '\0'), $($args),*).into_bytes();
188		// SAFETY: We null-terminated the string during formatting.
189		unsafe {
190			let name = std::ffi::CString::from_vec_with_nul_unchecked(name);
191			$crate::details::set_thread_name(name.as_ptr().cast());
192		}
193	}};
194}
195
196#[macro_export]
197#[cfg(all(not(doc), not(feature = "enabled")))]
198macro_rules! set_thread_name {
199	($name:literal) => {
200		// Silence unused expression warning.
201		_ = $name;
202	};
203
204	($format:literal, $($args:expr),*) => {{
205		// Silence unused expression warnings.
206		_ = $format;
207		$(
208			_ = $args;
209		)*
210	}};
211}
212
213/// Sends a message to Tracy's log.
214///
215/// Fast navigation in large data sets and correlating zones with what
216/// was happening in the application may be difficult. To ease these
217/// issues, Tracy provides a message log functionality. You can send
218/// messages (e.g. debug log output) using this macro.
219///
220/// # Examples
221///
222/// Just sending a message is straightforward:
223///
224/// ```no_run
225/// # use tracy_gizmos::*;
226/// message!("App started.");
227/// ```
228///
229/// Optionally, a custom [`Color`] could be assigned to the message.
230///
231/// ```no_run
232/// # use tracy_gizmos::*;
233/// message!(Color::YELLOW, "App failed to find something.");
234/// ```
235///
236/// ## Dynamic messages
237///
238/// It is also possible to use dynamic data as the message text.
239///
240/// The profiler will allocate a temporary internal buffer and copy
241/// the passed value there. Hence, this operation involves a small
242/// run-time cost.
243///
244/// Be aware that the passed text couldn't be larger than 64
245/// Kb.
246///
247/// ```no_run
248/// # use tracy_gizmos::*;
249/// # let i = 0;
250/// # let file_path = "file".to_string();
251/// message!("Trying {}", i);
252/// message!(&file_path);
253/// message!(Color::GREEN, "{} is good!", file_path);
254/// ```
255#[macro_export]
256#[cfg(any(doc, feature = "enabled"))]
257macro_rules! message {
258	($text:literal) => {
259		// SAFETY: We null-terminate the string.
260		unsafe {
261			$crate::details::message(concat!($text, '\0').as_ptr());
262		}
263	};
264
265	($text:expr) => {
266		$crate::details::message_size($text);
267	};
268
269	($format:literal, $($args:expr),*) => {
270		let _text = format!($format, $($args),*);
271		$crate::details::message_size(&_text);
272	};
273
274	($color:expr, $text:literal) => {
275		// SAFETY: We null-terminate the string.
276		unsafe {
277			$crate::details::message_color(
278				concat!($text, '\0').as_ptr(),
279				$color,
280			);
281		}
282	};
283
284	($color:expr, $text:expr) => {
285		$crate::details::message_size_color(
286			$text,
287			$color,
288		);
289	};
290
291	($color:expr, $format:literal, $($args:expr),*) => {
292		let _text = format!($format, $($args),*);
293		$crate::details::message_size_color(&_text, $color);
294	};
295}
296
297#[macro_export]
298#[cfg(all(not(doc), not(feature = "enabled")))]
299macro_rules! message {
300	($text:literal) => {};
301
302	($whatever:expr $(, $text:literal)?) => {
303		// Silences unused expression warning.
304		_ = $whatever;
305	};
306
307	($format:literal, $($args:expr),*) => {
308		// Silence unused expression warnings.
309		$(
310			_ = $args;
311		)*
312	};
313
314	($color:expr, $text:expr) => {
315		// Silence unused expression warnings.
316		_ = $color;
317		_ = $text;
318	};
319
320	($color:expr, $format:literal, $($args:expr),*) => {
321		// Silence unused expression warnings.
322		_ = $color;
323		$(
324			_ = $args;
325		)*
326	};
327}
328
329/// Marks the completed frame end moment.
330///
331/// Program's execution could happen in frame-sized chunks, e.g. a
332/// rendering or I/O driven loop.
333///
334/// Note, that this is fully optional, as lots of applications do not
335/// even us the concept of a frame.
336///
337/// Each frame starts *immediately* after previous one has ended.
338///
339/// Some types of frames are discontinuous by their natures - they are
340/// executed periodically, with a pause between each run. E.g. a
341/// physics processing step in a game loop or an audio or save
342/// callback on a separate thread. Tracy can also track this kind of
343/// frames.
344///
345/// Frame types *must not* be mixed. For each frame set, identified by
346/// an unique name, use either a continuous or discontinuous frame
347/// only!
348///
349/// Under the hood it declares a local [`Frame`].
350///
351/// # Examples
352///
353/// Here comes all possible and interesting cases of frame marking.
354///
355/// ## Main frame
356///
357/// As simple as:
358///
359/// ```no_run
360/// # use tracy_gizmos::*;
361/// # fn update() { todo!() }
362/// # fn render() { todo!() }
363/// loop {
364///     update();
365///     render();
366///     frame!();
367/// }
368/// ```
369///
370/// ## Secondary frame sets
371///
372/// It works in the same way as the main frame, but requires a unique
373/// name to identify this frame set:
374///
375/// ```no_run
376/// # use tracy_gizmos::*;
377/// # fn update()      { todo!() }
378/// # fn render()      { todo!() }
379/// # fn update_bots() { todo!() }
380/// let ai = std::thread::spawn(|| {
381///    loop {
382///        update_bots();
383///        frame!("ai");
384///    }
385/// });
386///
387/// loop {
388///     update();
389///     render();
390///     frame!();
391/// }
392/// ```
393///
394/// ## Discontinuous frames
395///
396/// As discontinuous frame doesn't start immediately after previous
397/// one has ended, you need to manually mark the frame's scope:
398///
399/// ```no_run
400/// # use tracy_gizmos::*;
401/// fn do_io_request() {
402///     // This declares the `_io` variable containing a guard, which
403///     // marks the frame end when dropped.
404///     frame!(_io, "IO");
405///
406///     // do the I/O work.
407/// }
408/// ```
409#[macro_export]
410#[cfg(any(doc, feature = "enabled"))]
411macro_rules! frame {
412	() => {
413		// SAFETY: Null pointer means main frame.
414		unsafe {
415			$crate::details::mark_frame_end(std::ptr::null());
416		}
417	};
418
419	($name:literal) => {
420		// SAFETY: We null-terminate the string.
421		unsafe {
422			$crate::details::mark_frame_end(concat!($name, '\0').as_ptr());
423		}
424	};
425
426	($var:ident, $name:literal) => {
427		#[allow(unused_variables)]
428		// SAFETY: We null-terminate the string.
429		let $var = unsafe {
430			$crate::details::discontinuous_frame(concat!($name, '\0').as_ptr().cast())
431		};
432	};
433}
434
435#[macro_export]
436#[cfg(all(not(doc), not(feature = "enabled")))]
437macro_rules! frame {
438	($($name:literal)? $($var:ident, $n:literal)?) => {
439		// $var could be used to denote a lexically scoped frame or
440		// even be manually `drop`-ed. Hence, we need to define it to
441		// keep the macro-using code compilable.
442		$(
443			#[allow(unused_variables)]
444			let $var = $crate::Frame();
445		)?
446	};
447}
448
449#[cfg(feature = "enabled")]
450static STARTED: AtomicBool = AtomicBool::new(false);
451
452/// Starts the Tracy capture.
453///
454/// Must be called *before* any other Tracy usage.
455///
456/// # Panics
457///
458/// Only one active capture can exist. Hence any consecutive
459/// `start_capture()` will panic, unless previously started capture is
460/// dropped.
461///
462/// # Examples
463///
464/// ```no_run
465/// let _tracy = tracy_gizmos::start_capture();
466/// ```
467pub fn start_capture() -> TracyCapture {
468	#[cfg(feature = "enabled")]
469	{
470		if STARTED.swap(true, Ordering::Acquire) {
471			panic!("Tracy capture has been started already.");
472		}
473		// SAFETY: Check above ensures this happens once.
474		unsafe {
475			sys::___tracy_startup_profiler();
476		}
477	}
478
479	TracyCapture(PhantomData)
480}
481
482/// Represents an active Tracy capture.
483///
484/// Obtaining a [`TracyCapture`] is *required* to instrument the code.
485/// Otherwise, the behaviour of instrumenting is undefined.
486///
487/// It is not allowed to have multiple copies of the [`TracyCapture`].
488///
489/// When it is dropped, the Tracy connection will be shutdown, which
490/// will also finish the capture.
491pub struct TracyCapture(PhantomData<*mut ()>);
492
493impl TracyCapture {
494	/// Returns `true` if a connection is currently established with
495	/// the Tracy server.
496	///
497	/// # Examples
498	///
499	/// This method can be used to ensure the profiler connection
500	/// _before_ doing any profiled work.
501	///
502	/// ```no_run
503	/// let tracy = tracy_gizmos::start_capture();
504	/// while !tracy.is_connected() {
505	///     std::thread::yield_now();
506	/// }
507	/// // You can do the profiling here knowing it will reach
508	/// // Tracy.
509	/// ```
510	///
511	/// You can also enabled `no-exit` feature instead, so
512	/// [`TracyCapture`] will do a blocking wait for the profiling data
513	/// to be transfered to the server, when dropped.
514	pub fn is_connected(&self) -> bool {
515		#[cfg(feature = "enabled")]
516		// SAFETY: self could exist only if startup was issued and
517		// succeeded.
518		unsafe {
519			sys::___tracy_connected() != 0
520		}
521
522		#[cfg(not(feature = "enabled"))]
523		true
524	}
525}
526
527#[cfg(feature = "enabled")]
528impl Drop for TracyCapture {
529	fn drop(&mut self) {
530		// SAFETY: self could exist only if startup was issued and
531		// succeeded.
532		unsafe {
533			sys::___tracy_shutdown_profiler();
534		}
535		STARTED.store(false, Ordering::Release);
536	}
537}
538
539/// Instruments the current scope with a profiling zone.
540///
541/// A zone represents the lifetime of a special on-stack profiler
542/// variable. Typically, it would exist for the duration of a whole
543/// profiled function scope, but it is also possible to measure time
544/// spent in nested loops or if branches.
545///
546/// Under the hood it declares a local [`Zone`].
547///
548/// A custom name is required for the zone, which allows to identify
549/// it later in the Trace visualization.
550///
551/// This will automatically record source file name, and location.
552///
553/// # Examples
554///
555/// Just adding a profiling zone is as easy as:
556///
557/// ```no_run
558/// # use tracy_gizmos::*;
559/// fn do_stuff() {
560///     zone!("stuff");
561///     // now actually do stuff :-)
562/// }
563/// ```
564///
565/// Optionally, a custom [`Color`] could be assigned for to zone.
566/// Note, that the color value will be constant in the recording.
567///
568/// ```no_run
569/// # use tracy_gizmos::*;
570/// fn do_stuff() {
571///     zone!("stuff", Color::BISQUE);
572///     // now actually do stuff :-)
573/// }
574/// ```
575///
576/// ## Nesting
577///
578/// Multiple active zones can exist and they will be nested in
579/// parent-child relationship automatically by Tracy.
580///
581/// ```no_run
582/// # use tracy_gizmos::*;
583/// # fn work() {}
584/// fn work_cycle(times: usize) {
585///     zone!("doing work");
586///     for _ in 0..times {
587///         zone!("unit of work");
588///         work();
589///     }
590/// }
591/// ```
592///
593/// ## Filtering zones
594///
595/// Zone logging can be disabled on a per-zone basis:
596///
597/// ```no_run
598/// # use tracy_gizmos::*;
599/// const PROFILE_JOBS: bool = false;
600/// zone!("Do Jobs", enabled: PROFILE_JOBS); // no runtime cost.
601/// ```
602///
603/// Note that this parameter may be a run-time expression, which
604/// should evaluate to `bool`, such as a user-controller switch to
605/// enable the profiling of a specific part of the code only when
606/// needed.
607///
608/// ```no_run
609/// # use tracy_gizmos::*;
610/// # fn toggled() -> bool { true }
611/// # let mut do_profiles = false;
612/// if toggled() {
613///    do_profiles = !do_profiles;
614/// }
615///
616/// zone!("Do Jobs", enabled: do_profiles); // runtime cost.
617/// ```
618///
619/// ## Dynamic data
620///
621/// It is possible to have a set of dynamic data attached to the
622/// particular zone. Refer to [`Zone`] for more details.
623///
624/// ```no_run
625/// # use tracy_gizmos::*;
626/// # let file_path = "./main.rs";
627/// zone!(parsing, "Parsing");
628/// parsing.text(file_path);
629/// ```
630#[macro_export]
631#[cfg(any(doc, feature = "enabled"))]
632macro_rules! zone {
633	(            $name:literal)                               => { $crate::zone!(_z,   $name, $crate::Color::UNSPECIFIED, enabled:true) };
634	($var:ident, $name:literal)                               => { $crate::zone!($var, $name, $crate::Color::UNSPECIFIED, enabled:true) };
635	(            $name:literal, $color:expr)                  => { $crate::zone!(_z,   $name, $color,                     enabled:true) };
636	($var:ident, $name:literal, $color:expr)                  => { $crate::zone!($var, $name, $color,                     enabled:true) };
637	(            $name:literal,              enabled:$e:expr) => { $crate::zone!(_z,   $name, $crate::Color::UNSPECIFIED, enabled:$e)   };
638	($var:ident, $name:literal,              enabled:$e:expr) => { $crate::zone!($var, $name, $crate::Color::UNSPECIFIED, enabled:$e)   };
639	(            $name:literal, $color:expr, enabled:$e:expr) => { $crate::zone!(_z,   $name, $color,                     enabled:$e)   };
640	($var:ident, $name:literal, $color:expr, enabled:$e:expr) => {
641		#[allow(unused_variables)]
642		// SAFETY: This macro ensures that location & context data are correct.
643		let $var = unsafe {
644			$crate::details::zone($crate::zone!(@loc $name, $color), if $e {1} else {0})
645		};
646	};
647
648	(@loc $name:literal, $color: expr) => {{
649		// This is an implementation detail and can be changed at any moment.
650
651		#[cfg(feature = "unstable-function-names")]
652		struct X;
653		#[cfg(feature = "unstable-function-names")]
654		const FUNCTION: &'static [u8] = {
655			&$crate::details::get_fn_name_from_nested_type::<X>()
656		};
657
658		#[cfg(not(feature = "unstable-function-names"))]
659		const FUNCTION: &'static [u8] = b"<unavailable>\0";
660
661		// SAFETY: All passed data is created here and is correct.
662		static LOC: $crate::ZoneLocation = unsafe {
663			$crate::details::zone_location(
664				concat!($name, '\0'),
665				FUNCTION,
666				concat!(file!(), '\0'),
667				line!(),
668				$crate::Color::as_u32(&$color),
669			)
670		};
671		&LOC
672	}};
673}
674
675#[macro_export]
676#[cfg(all(not(doc), not(feature = "enabled")))]
677macro_rules! zone {
678	($($var:ident,)? $name:literal, enabled:$e:expr) => {
679		// Silences unused enabled expression warning.
680		_ = $e;
681		$crate::zone!($($var,)? $name, (), enabled:$e);
682	};
683
684	($($var:ident,)? $name:literal $(,$color:expr)? $(,enabled:$e:expr)?) => {
685		// $var could be used to add dynamic zone data, so we need to
686		// define it to keep the macro-using code compilable.
687		$(
688			#[allow(unused_variables)]
689			let $var = $crate::Zone::new();
690		)?
691		// Silences unused `Color` import warning.
692		$(
693			_ = $color;
694		)?
695	};
696}
697
698/// Profiling zone.
699///
700/// Refer to [`zone!`] for the usage how-to.
701///
702/// It instruments the current scope. Hence, the profiling zone will
703/// end when [`Zone`] is dropped.
704pub struct Zone {
705	#[cfg(feature = "enabled")]
706	ctx:     sys::TracyCZoneCtx,
707	_unsend: PhantomData<*mut ()>,
708}
709
710#[cfg(any(doc, feature = "enabled"))]
711impl Drop for Zone {
712	#[inline(always)]
713	fn drop(&mut self) {
714		#[cfg(feature = "enabled")]
715		// SAFETY: The only way to have Zone is to construct it via
716		// zone! macro, which ensures that ctx value is correct.
717		unsafe {
718			sys::___tracy_emit_zone_end(self.ctx);
719		}
720	}
721}
722
723impl Zone {
724	#[doc(hidden)]
725	#[cfg(not(feature = "enabled"))]
726	pub fn new() -> Self {
727		Self { _unsend: PhantomData }
728	}
729
730	/// Allows to control the zone color dynamically.
731	///
732	/// This can be called multiple times, however only the latest
733	/// call will have an effect.
734	pub fn color(&self, color: Color) {
735		#[cfg(feature = "enabled")]
736		// SAFETY: self always contains a valid `ctx`.
737		unsafe {
738			sys::___tracy_emit_zone_color(self.ctx, color.as_u32());
739		}
740		#[cfg(not(feature = "enabled"))]
741		{
742			// Silences unused import warning.
743			_ = color;
744		}
745	}
746
747	/// Adds a custom numeric value that will be displayed along with
748	/// the zone information. E.g. a loop iteration or size of the
749	/// processed buffer.
750	///
751	/// This method can be called multiple times, all of the passed
752	/// values will be attached to the zone matching the call order.
753	#[cfg(feature = "enabled")]
754	pub fn number(&self, value: u64) {
755		#[cfg(feature = "enabled")]
756		// SAFETY: self always contains a valid `ctx`.
757		unsafe {
758			sys::___tracy_emit_zone_value(self.ctx, value);
759		}
760	}
761
762	/// Adds a custom text string that will be displayed along with
763	/// the zone information. E.g. name of the file you are
764	/// processing.
765	///
766	/// The profiler will allocate a temporary internal buffer and
767	/// copy the passed value there. Hence, this operation involves a
768	/// small run-time cost.
769	///
770	/// This method can be called multiple times, all of the passed
771	/// values will be attached to the zone matching the call order.
772	///
773	/// Be aware that the passed text slice couldn't be larger than 64
774	/// Kb.
775	pub fn text(&self, s: &str) {
776		#[cfg(feature = "enabled")]
777		{
778			debug_assert!(s.len() < u16::MAX as usize);
779			// SAFETY: self always contains a valid `ctx`.
780			unsafe {
781				sys::___tracy_emit_zone_text(self.ctx, s.as_ptr().cast(), s.len())
782			}
783		}
784	}
785}
786
787/// A statically allocated location for a profiling zone.
788///
789/// It is an implementation detail and can be changed at any moment.
790#[doc(hidden)]
791#[repr(transparent)]
792pub struct ZoneLocation(#[cfg(feature = "enabled")] sys::___tracy_source_location_data);
793
794// SAFETY: It is fully static and constant.
795unsafe impl Send for ZoneLocation {}
796unsafe impl Sync for ZoneLocation {}
797
798/// Discontinuous frame.
799///
800/// Refer to [`frame!`] for usage howto.
801///
802/// It instruments the current frame scope. Hence, the discontinuous
803/// frame will be marked as finished when [`Frame`] is dropped.
804pub struct Frame(#[cfg(feature = "enabled")] *const i8);
805
806#[cfg(any(doc, feature = "enabled"))]
807impl Drop for Frame {
808	#[inline(always)]
809	fn drop(&mut self) {
810		#[cfg(feature = "enabled")]
811		// SAFETY: The only way to have Frame is to construct it via
812		// frame! macro, which ensures that contained pointer is
813		// correct.
814		unsafe {
815			sys::___tracy_emit_frame_mark_end(self.0);
816		}
817	}
818}
819
820/// Tracy can collect additional information about the profiled
821/// application, which will be available in the trace description.
822/// This can include data such as the source repository revision,
823/// crate version, application's environment, etc.
824///
825/// This can be called multiple times. Tracy will accumulate all the
826/// information and display it altogether.
827///
828/// Be aware that the passed text slice couldn't be larger than 64
829/// Kb.
830///
831/// ```no_run
832/// # use tracy_gizmos::*;
833/// app_info("My fancy application");
834/// app_info(env!("CARGO_PKG_VERSION"));
835/// ```
836#[inline(always)]
837pub fn app_info(info: &str) {
838	#[cfg(feature = "enabled")]
839	{
840		debug_assert!(info.len() < u16::MAX as usize);
841		// SAFETY: Slice should contain valid data and having no
842		// terminating zero is fine.
843		unsafe {
844			sys::___tracy_emit_message_appinfo(info.as_ptr().cast(), info.len());
845		}
846	}
847}
848
849/// Implementation details, do not relay on anything from this module!
850///
851/// It is public only due to the usage in public macro bodies.
852#[doc(hidden)]
853#[cfg(feature = "enabled")]
854pub mod details {
855	use std::ffi::c_void;
856	use super::*;
857
858	#[inline(always)]
859	pub const unsafe fn zone_location(
860		name: &'static str,
861		func: &'static [u8],
862		file: &'static str,
863		line: u32,
864		color: u32,
865	) -> ZoneLocation {
866		ZoneLocation(
867			sys::___tracy_source_location_data {
868				name:     name.as_ptr().cast(),
869				function: func.as_ptr().cast(),
870				file:     file.as_ptr().cast(),
871				line,
872				color,
873			}
874		)
875	}
876
877	#[inline(always)]
878	pub unsafe fn zone(location: &ZoneLocation, enabled: i32) -> Zone {
879		let ctx = sys::___tracy_emit_zone_begin(&location.0, enabled);
880		Zone { ctx, _unsend: PhantomData }
881	}
882
883	#[inline(always)]
884	pub unsafe fn set_thread_name(name: *const u8) {
885		sys::___tracy_set_thread_name(name.cast());
886	}
887
888	#[inline(always)]
889	pub unsafe fn message(text: *const u8) {
890		sys::___tracy_emit_messageL(
891			text.cast(),
892			0, // callstack depth, 0 is disabled.
893		);
894	}
895
896	#[inline(always)]
897	pub fn message_size(text: &str) {
898		debug_assert!(text.len() < u16::MAX as usize);
899		// SAFETY: Dynamic non-zero-terminated string is fine.
900		unsafe {
901			sys::___tracy_emit_message(
902				text.as_ptr().cast(),
903				text.len(),
904				0, // callstack depth, 0 is disabled.
905			);
906		}
907	}
908
909	#[inline(always)]
910	pub fn message_size_color(text: &str, color: Color) {
911		debug_assert!(text.len() < u16::MAX as usize);
912		// SAFETY: Dynamic non-zero-terminated string is fine.
913		unsafe {
914			sys::___tracy_emit_messageC(
915				text.as_ptr().cast(),
916				text.len(),
917				color.as_u32(),
918				0, // callstack depth, 0 is disabled.
919			);
920		}
921	}
922
923	#[inline(always)]
924	pub unsafe fn message_color(text: *const u8, color: Color) {
925		sys::___tracy_emit_messageLC(
926			text.cast(),
927			color.as_u32(),
928			0, // callstack depth, 0 is disabled.
929		);
930	}
931
932	#[inline(always)]
933	pub unsafe fn mark_frame_end(name: *const u8) {
934		sys::___tracy_emit_frame_mark(name.cast());
935	}
936
937	#[inline(always)]
938	pub unsafe fn discontinuous_frame(name: *const i8) -> Frame {
939		sys::___tracy_emit_frame_mark_start(name);
940		Frame(name)
941	}
942
943	#[inline(always)]
944	pub unsafe fn track_alloc<T>(name: *const u8, ptr: *const T, size: usize) {
945		track_alloc_impl(name, ptr.cast(), size);
946	}
947
948	#[inline(always)]
949	unsafe fn track_alloc_impl(name: *const u8, ptr: *const c_void, size: usize) {
950		sys::___tracy_emit_memory_alloc_named(ptr, size, 0, name.cast());
951	}
952
953	#[inline(always)]
954	pub unsafe fn track_free<T>(name: *const u8, ptr: *const T) {
955		track_free_impl(name, ptr.cast());
956	}
957
958	#[inline(always)]
959	unsafe fn track_free_impl(name: *const u8, ptr: *const c_void) {
960		sys::___tracy_emit_memory_free_named(ptr, 0, name.cast());
961	}
962
963	// Function name trick only works with an unstable
964	// feature, which provides const `type_name`. Tracking
965	// issue on the Rust side:
966	// https://github.com/rust-lang/rust/issues/63084
967	#[cfg(feature = "unstable-function-names")]
968	pub const fn get_fn_name_from_nested_type<T>() -> [u8; std::any::type_name::<T>().len() - 2]
969	where
970		[(); std::any::type_name::<T>().len() - 2]:
971	{
972		let bytes   = std::any::type_name::<T>().as_bytes();
973		// We skip (-3 + 1) of the type name length, to skip the '::X' suffix and add the terminating zero.
974		let mut buf = [0; std::any::type_name::<T>().len() - 2];
975		let n       = buf.len() - 1;
976		let mut i   = 0;
977
978		while i < n {
979			buf[i] = bytes[i];
980			i += 1;
981		}
982
983		buf
984	}
985
986	// Function above could be replaced by the following code directly
987	// in the macro body, when const_type_name is stable.
988	//
989	// const FUNCTION: &'static [u8] = {
990	// 	struct X;
991	// 	const TYPE_NAME: &'static str = std::any::type_name::<X>();
992	// 	// We skip 3 of the '::X' suffix and add 1 for the terminating zero.
993	// 	const FUNCTION_LEN: usize = TYPE_NAME.len() - 3 + 1;
994	// 	&$crate::details::as_array::<FUNCTION_LEN>(TYPE_NAME)
995	// };
996	// pub const fn as_array<const N: usize>(s: &'static str) -> [u8; N] {
997	// 	let bytes   = s.as_bytes();
998	// 	let mut buf = [0; N];
999	// 	let mut i   = 0;
1000	// 	while i < N - 1 {
1001	// 		buf[i] = bytes[i];
1002	// 		i += 1;
1003	// 	}
1004	// 	buf
1005	// }
1006}
1007
1008#[cfg(test)]
1009mod tests {
1010	#[cfg(feature = "enabled")]
1011    use super::*;
1012
1013	#[cfg(feature = "enabled")]
1014	#[test]
1015	fn double_lifecycle() {
1016		let tracy = start_capture();
1017		drop(tracy);
1018		let _tracy = start_capture();
1019	}
1020
1021	#[cfg(feature = "enabled")]
1022	#[test]
1023	#[should_panic]
1024	fn double_start_fails() {
1025		let _tracy1 = start_capture();
1026		let _tracy2 = start_capture();
1027	}
1028}