Skip to main content

bp3d_debug/profiler/
section.rs

1// Copyright (c) 2026, BlockProject 3D
2//
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without modification,
6// are permitted provided that the following conditions are met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//     * Redistributions in binary form must reproduce the above copyright notice,
11//       this list of conditions and the following disclaimer in the documentation
12//       and/or other materials provided with the distribution.
13//     * Neither the name of BlockProject 3D nor the names of its contributors
14//       may be used to endorse or promote products derived from this software
15//       without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29use super::instant::get_time;
30use crate::field::FieldSet;
31use crate::util::Location;
32use std::num::NonZeroU32;
33use std::sync::OnceLock;
34
35#[repr(u8)]
36#[derive(Debug, Copy, Clone, Eq, PartialEq)]
37pub enum Level {
38    /// A section located in a critically hot path.
39    Critical = 0,
40
41    /// A periodic section.
42    Periodic = 1,
43
44    // An event based section.
45    Event = 2,
46}
47
48pub struct Entered<'a, const N: usize> {
49    id: NonZeroU32,
50    start: u64,
51    fields: FieldSet<'a, N>,
52}
53
54impl<const N: usize> Drop for Entered<'_, N> {
55    fn drop(&mut self) {
56        let end = get_time();
57        crate::engine::get().section_record(self.id, self.start, end, self.fields.as_ref());
58    }
59}
60
61pub struct Section {
62    name: &'static str,
63    location: Location,
64    level: Level,
65    parent: Option<&'static Section>,
66    id: OnceLock<NonZeroU32>,
67}
68
69impl Section {
70    pub const fn new(name: &'static str, location: Location, level: Level) -> Self {
71        Self {
72            name,
73            location,
74            level,
75            parent: None,
76            id: OnceLock::new(),
77        }
78    }
79
80    pub const fn set_parent(mut self, parent: &'static Section) -> Self {
81        self.parent = Some(parent);
82        self
83    }
84
85    pub fn name(&self) -> &'static str {
86        self.name
87    }
88
89    pub fn location(&self) -> &Location {
90        &self.location
91    }
92
93    pub fn level(&self) -> Level {
94        self.level
95    }
96
97    pub fn parent(&self) -> Option<&'static Section> {
98        self.parent
99    }
100
101    pub fn get_id(&'static self) -> &'static NonZeroU32 {
102        self.id
103            .get_or_init(|| crate::engine::get().section_register(self))
104    }
105
106    pub fn enter<'a, const N: usize>(&'static self, fields: FieldSet<'a, N>) -> Entered<'a, N> {
107        let id = self.get_id();
108        Entered {
109            id: *id,
110            start: get_time(),
111            fields,
112        }
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use crate::field::FieldSet;
119    use crate::profiler::section::{Level, Section};
120    use crate::{fields, location, profiler_section_start};
121
122    #[test]
123    fn basic() {
124        static _SECTION: Section = Section::new("api_test", location!(), Level::Event);
125    }
126
127    #[test]
128    fn api_test() {
129        static SECTION: Section = Section::new("api_test", location!(), Level::Event);
130        static _SECTION2: Section =
131            Section::new("api_test2", location!(), Level::Event).set_parent(&SECTION);
132        SECTION.enter(FieldSet::new(fields!()));
133        SECTION.enter(FieldSet::new(fields!({ test = 42 })));
134        SECTION.enter(FieldSet::new(fields!({ test = "test 123" })));
135        SECTION.enter(FieldSet::new(fields!({ test = 42.42 })));
136        SECTION.enter(FieldSet::new(fields!({test=?Level::Event})));
137        SECTION.enter(FieldSet::new(fields!({test=?Level::Event} {test2=42})));
138        let value = 32;
139        let str = "this is a test";
140        let lvl = Level::Event;
141        SECTION.enter(FieldSet::new(fields!({value} {str} {?lvl} {test = value})));
142    }
143
144    #[test]
145    fn api_test2() {
146        let value = 32;
147        let str = "this is a test";
148        let lvl = Level::Event;
149        profiler_section_start!(API_TEST, Level::Event);
150        profiler_section_start!(API2_TEST: API_TEST, Level::Event);
151        profiler_section_start!(API3_TEST_WITH_PARAMS: API2_TEST, Level::Event, {value} {str} {?lvl} {test=value});
152    }
153}