1
+ use std:: hash:: { Hash , Hasher } ;
2
+ use std:: { borrow:: Cow , collections:: VecDeque } ;
3
+
1
4
use bevy_app:: App ;
2
5
use bevy_ecs:: system:: { Deferred , Res , Resource , SystemBuffer , SystemParam } ;
3
- use bevy_log:: warn;
4
- use bevy_utils:: { Duration , Instant , StableHashMap , Uuid } ;
5
- use std:: { borrow:: Cow , collections:: VecDeque } ;
6
+ use bevy_utils:: { hashbrown:: HashMap , Duration , Instant , PassHash } ;
7
+ use const_fnv1a_hash:: fnv1a_hash_str_64;
8
+
9
+ use crate :: DEFAULT_MAX_HISTORY_LENGTH ;
10
+
11
+ /// Unique diagnostic path, separated by `/`.
12
+ ///
13
+ /// Requirements:
14
+ /// - Can't be empty
15
+ /// - Can't have leading or trailing `/`
16
+ /// - Can't have empty components.
17
+ #[ derive( Debug , Clone ) ]
18
+ pub struct DiagnosticPath {
19
+ path : Cow < ' static , str > ,
20
+ hash : u64 ,
21
+ }
22
+
23
+ impl DiagnosticPath {
24
+ /// Create a new `DiagnosticPath`. Usable in const contexts.
25
+ ///
26
+ /// **Note**: path is not validated, so make sure it follows all the requirements.
27
+ pub const fn const_new ( path : & ' static str ) -> DiagnosticPath {
28
+ DiagnosticPath {
29
+ path : Cow :: Borrowed ( path) ,
30
+ hash : fnv1a_hash_str_64 ( path) ,
31
+ }
32
+ }
33
+
34
+ /// Create a new `DiagnosticPath` from the specified string.
35
+ pub fn new ( path : impl Into < Cow < ' static , str > > ) -> DiagnosticPath {
36
+ let path = path. into ( ) ;
37
+
38
+ debug_assert ! ( !path. is_empty( ) , "diagnostic path can't be empty" ) ;
39
+ debug_assert ! (
40
+ !path. starts_with( '/' ) ,
41
+ "diagnostic path can't be start with `/`"
42
+ ) ;
43
+ debug_assert ! (
44
+ !path. ends_with( '/' ) ,
45
+ "diagnostic path can't be end with `/`"
46
+ ) ;
47
+ debug_assert ! (
48
+ !path. contains( "//" ) ,
49
+ "diagnostic path can't contain empty components"
50
+ ) ;
51
+
52
+ DiagnosticPath {
53
+ hash : fnv1a_hash_str_64 ( & path) ,
54
+ path,
55
+ }
56
+ }
57
+
58
+ /// Create a new `DiagnosticPath` from an iterator over components.
59
+ pub fn from_components < ' a > ( components : impl IntoIterator < Item = & ' a str > ) -> DiagnosticPath {
60
+ let mut buf = String :: new ( ) ;
61
+
62
+ for ( i, component) in components. into_iter ( ) . enumerate ( ) {
63
+ if i > 0 {
64
+ buf. push ( '/' ) ;
65
+ }
66
+ buf. push_str ( component) ;
67
+ }
68
+
69
+ DiagnosticPath :: new ( buf)
70
+ }
6
71
7
- use crate :: MAX_DIAGNOSTIC_NAME_WIDTH ;
72
+ /// Returns full path, joined by `/`
73
+ pub fn as_str ( & self ) -> & str {
74
+ & self . path
75
+ }
8
76
9
- /// Unique identifier for a [`Diagnostic`].
10
- #[ derive( Debug , Copy , Clone , Hash , Eq , PartialEq , PartialOrd , Ord ) ]
11
- pub struct DiagnosticId ( pub Uuid ) ;
77
+ /// Returns an iterator over path components.
78
+ pub fn components ( & self ) -> impl Iterator < Item = & str > + ' _ {
79
+ self . path . split ( '/' )
80
+ }
81
+ }
12
82
13
- impl DiagnosticId {
14
- pub const fn from_u128 ( value : u128 ) -> Self {
15
- DiagnosticId ( Uuid :: from_u128 ( value ) )
83
+ impl From < DiagnosticPath > for String {
84
+ fn from ( path : DiagnosticPath ) -> Self {
85
+ path . path . into ( )
16
86
}
17
87
}
18
88
19
- impl Default for DiagnosticId {
20
- fn default ( ) -> Self {
21
- DiagnosticId ( Uuid :: new_v4 ( ) )
89
+ impl Eq for DiagnosticPath { }
90
+
91
+ impl PartialEq for DiagnosticPath {
92
+ fn eq ( & self , other : & Self ) -> bool {
93
+ self . hash == other. hash && self . path == other. path
94
+ }
95
+ }
96
+
97
+ impl Hash for DiagnosticPath {
98
+ fn hash < H : Hasher > ( & self , state : & mut H ) {
99
+ state. write_u64 ( self . hash ) ;
100
+ }
101
+ }
102
+
103
+ impl std:: fmt:: Display for DiagnosticPath {
104
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
105
+ self . path . fmt ( f)
22
106
}
23
107
}
24
108
@@ -33,8 +117,7 @@ pub struct DiagnosticMeasurement {
33
117
/// Diagnostic examples: frames per second, CPU usage, network latency
34
118
#[ derive( Debug ) ]
35
119
pub struct Diagnostic {
36
- pub id : DiagnosticId ,
37
- pub name : Cow < ' static , str > ,
120
+ path : DiagnosticPath ,
38
121
pub suffix : Cow < ' static , str > ,
39
122
history : VecDeque < DiagnosticMeasurement > ,
40
123
sum : f64 ,
@@ -71,34 +154,29 @@ impl Diagnostic {
71
154
self . history . push_back ( measurement) ;
72
155
}
73
156
74
- /// Create a new diagnostic with the given ID, name and maximum history.
75
- pub fn new (
76
- id : DiagnosticId ,
77
- name : impl Into < Cow < ' static , str > > ,
78
- max_history_length : usize ,
79
- ) -> Diagnostic {
80
- let name = name. into ( ) ;
81
- if name. chars ( ) . count ( ) > MAX_DIAGNOSTIC_NAME_WIDTH {
82
- // This could be a false positive due to a unicode width being shorter
83
- warn ! (
84
- "Diagnostic {:?} has name longer than {} characters, and so might overflow in the LogDiagnosticsPlugin\
85
- Consider using a shorter name.",
86
- name, MAX_DIAGNOSTIC_NAME_WIDTH
87
- ) ;
88
- }
157
+ /// Create a new diagnostic with the given path.
158
+ pub fn new ( path : DiagnosticPath ) -> Diagnostic {
89
159
Diagnostic {
90
- id,
91
- name,
160
+ path,
92
161
suffix : Cow :: Borrowed ( "" ) ,
93
- history : VecDeque :: with_capacity ( max_history_length ) ,
94
- max_history_length,
162
+ history : VecDeque :: with_capacity ( DEFAULT_MAX_HISTORY_LENGTH ) ,
163
+ max_history_length : DEFAULT_MAX_HISTORY_LENGTH ,
95
164
sum : 0.0 ,
96
165
ema : 0.0 ,
97
166
ema_smoothing_factor : 2.0 / 21.0 ,
98
167
is_enabled : true ,
99
168
}
100
169
}
101
170
171
+ /// Set the maximum history length.
172
+ #[ must_use]
173
+ pub fn with_max_history_length ( mut self , max_history_length : usize ) -> Self {
174
+ self . max_history_length = max_history_length;
175
+ self . history . reserve ( self . max_history_length ) ;
176
+ self . history . shrink_to ( self . max_history_length ) ;
177
+ self
178
+ }
179
+
102
180
/// Add a suffix to use when logging the value, can be used to show a unit.
103
181
#[ must_use]
104
182
pub fn with_suffix ( mut self , suffix : impl Into < Cow < ' static , str > > ) -> Self {
@@ -122,6 +200,10 @@ impl Diagnostic {
122
200
self
123
201
}
124
202
203
+ pub fn path ( & self ) -> & DiagnosticPath {
204
+ & self . path
205
+ }
206
+
125
207
/// Get the latest measurement from this diagnostic.
126
208
#[ inline]
127
209
pub fn measurement ( & self ) -> Option < & DiagnosticMeasurement > {
@@ -198,31 +280,29 @@ impl Diagnostic {
198
280
/// A collection of [`Diagnostic`]s.
199
281
#[ derive( Debug , Default , Resource ) ]
200
282
pub struct DiagnosticsStore {
201
- // This uses a [`StableHashMap`] to ensure that the iteration order is deterministic between
202
- // runs when all diagnostics are inserted in the same order.
203
- diagnostics : StableHashMap < DiagnosticId , Diagnostic > ,
283
+ diagnostics : HashMap < DiagnosticPath , Diagnostic , PassHash > ,
204
284
}
205
285
206
286
impl DiagnosticsStore {
207
287
/// Add a new [`Diagnostic`].
208
288
///
209
289
/// If possible, prefer calling [`App::register_diagnostic`].
210
290
pub fn add ( & mut self , diagnostic : Diagnostic ) {
211
- self . diagnostics . insert ( diagnostic. id , diagnostic) ;
291
+ self . diagnostics . insert ( diagnostic. path . clone ( ) , diagnostic) ;
212
292
}
213
293
214
- pub fn get ( & self , id : DiagnosticId ) -> Option < & Diagnostic > {
215
- self . diagnostics . get ( & id )
294
+ pub fn get ( & self , path : & DiagnosticPath ) -> Option < & Diagnostic > {
295
+ self . diagnostics . get ( path )
216
296
}
217
297
218
- pub fn get_mut ( & mut self , id : DiagnosticId ) -> Option < & mut Diagnostic > {
219
- self . diagnostics . get_mut ( & id )
298
+ pub fn get_mut ( & mut self , path : & DiagnosticPath ) -> Option < & mut Diagnostic > {
299
+ self . diagnostics . get_mut ( path )
220
300
}
221
301
222
302
/// Get the latest [`DiagnosticMeasurement`] from an enabled [`Diagnostic`].
223
- pub fn get_measurement ( & self , id : DiagnosticId ) -> Option < & DiagnosticMeasurement > {
303
+ pub fn get_measurement ( & self , path : & DiagnosticPath ) -> Option < & DiagnosticMeasurement > {
224
304
self . diagnostics
225
- . get ( & id )
305
+ . get ( path )
226
306
. filter ( |diagnostic| diagnostic. is_enabled )
227
307
. and_then ( |diagnostic| diagnostic. measurement ( ) )
228
308
}
@@ -249,27 +329,27 @@ impl<'w, 's> Diagnostics<'w, 's> {
249
329
/// Add a measurement to an enabled [`Diagnostic`]. The measurement is passed as a function so that
250
330
/// it will be evaluated only if the [`Diagnostic`] is enabled. This can be useful if the value is
251
331
/// costly to calculate.
252
- pub fn add_measurement < F > ( & mut self , id : DiagnosticId , value : F )
332
+ pub fn add_measurement < F > ( & mut self , path : & DiagnosticPath , value : F )
253
333
where
254
334
F : FnOnce ( ) -> f64 ,
255
335
{
256
336
if self
257
337
. store
258
- . get ( id )
338
+ . get ( path )
259
339
. filter ( |diagnostic| diagnostic. is_enabled )
260
340
. is_some ( )
261
341
{
262
342
let measurement = DiagnosticMeasurement {
263
343
time : Instant :: now ( ) ,
264
344
value : value ( ) ,
265
345
} ;
266
- self . queue . 0 . insert ( id , measurement) ;
346
+ self . queue . 0 . insert ( path . clone ( ) , measurement) ;
267
347
}
268
348
}
269
349
}
270
350
271
351
#[ derive( Default ) ]
272
- struct DiagnosticsBuffer ( StableHashMap < DiagnosticId , DiagnosticMeasurement > ) ;
352
+ struct DiagnosticsBuffer ( HashMap < DiagnosticPath , DiagnosticMeasurement , PassHash > ) ;
273
353
274
354
impl SystemBuffer for DiagnosticsBuffer {
275
355
fn apply (
@@ -278,8 +358,8 @@ impl SystemBuffer for DiagnosticsBuffer {
278
358
world : & mut bevy_ecs:: world:: World ,
279
359
) {
280
360
let mut diagnostics = world. resource_mut :: < DiagnosticsStore > ( ) ;
281
- for ( id , measurement) in self . 0 . drain ( ) {
282
- if let Some ( diagnostic) = diagnostics. get_mut ( id ) {
361
+ for ( path , measurement) in self . 0 . drain ( ) {
362
+ if let Some ( diagnostic) = diagnostics. get_mut ( & path ) {
283
363
diagnostic. add_measurement ( measurement) ;
284
364
}
285
365
}
@@ -298,12 +378,12 @@ impl RegisterDiagnostic for App {
298
378
///
299
379
/// ```
300
380
/// use bevy_app::App;
301
- /// use bevy_diagnostic::{Diagnostic, DiagnosticsPlugin, DiagnosticId , RegisterDiagnostic};
381
+ /// use bevy_diagnostic::{Diagnostic, DiagnosticsPlugin, DiagnosticPath , RegisterDiagnostic};
302
382
///
303
- /// const UNIQUE_DIAG_ID: DiagnosticId = DiagnosticId::from_u128(42 );
383
+ /// const UNIQUE_DIAG_PATH: DiagnosticPath = DiagnosticPath::const_new("foo/bar" );
304
384
///
305
385
/// App::new()
306
- /// .register_diagnostic(Diagnostic::new(UNIQUE_DIAG_ID, "example", 10 ))
386
+ /// .register_diagnostic(Diagnostic::new(UNIQUE_DIAG_PATH ))
307
387
/// .add_plugins(DiagnosticsPlugin)
308
388
/// .run();
309
389
/// ```
0 commit comments