bevy_mini_fps/
lib.rs

1#![doc = include_str!("../README.md")]
2
3#[doc(hidden)]
4pub use sysinfo;
5
6/// N is only allowed to be a power of 2.
7#[doc(hidden)]
8pub struct RingBuffer<const N: usize> {
9    buffer: [f32; N],
10    ptr: usize,
11    len: usize,
12}
13
14impl<const N: usize> RingBuffer<N> {
15    pub fn is_empty(&self) -> bool {
16        self.len == 0
17    }
18
19    pub fn len(&self) -> usize {
20        self.len
21    }
22
23    pub fn push(&mut self, item: f32) {
24        self.buffer[self.ptr] = item;
25        self.ptr = (self.ptr + 1) & (N - 1);
26        self.len = N.min(self.len + 1)
27    }
28
29    pub fn iter(&self) -> impl Iterator<Item = &f32> {
30        self.buffer[0..self.len].iter()
31    }
32}
33impl<const N: usize> Default for RingBuffer<N> {
34    fn default() -> Self {
35        Self { 
36            buffer: [0.0; N], 
37            ptr: 0, 
38            len: 0
39        }
40    }
41}
42
43#[macro_export]
44macro_rules! fps_plugin {
45    () => {{
46        
47use ::bevy::prelude::*;
48use ::core::fmt::Write;
49
50fn __entity_count(world: &World) -> usize {
51    world.entities().total_count()
52}
53
54#[allow(clippy::too_many_arguments)]
55fn __diagnostic_system(
56    entity_count: In<usize>,
57    mut commands: Commands,
58    mut sys_info: Local<$crate::sysinfo::System>,
59    mut refresh_timer: Local<f32>,
60    mut sys_timer: Local<f32>,
61    mut time_buffer: Local<$crate::RingBuffer<128>>,
62    cached: Local<::std::cell::OnceCell<[Entity; 5]>>,
63    time: Res<Time>,
64    mut text: Query<&mut Text>,
65) {
66    let [fps, max_ft, entities, cpu, ram] = *cached.get_or_init(|| {
67        let mut result = [Entity::PLACEHOLDER; 5];
68        const OUTER_MARGIN: f32 = 4.;
69        const INNER_MARGIN: f32 = 2.;
70        let font = TextFont {
71            font_size: 16.0,
72            ..default()
73        };
74        commands.spawn((
75            Node {
76                margin: UiRect { 
77                    left: Val::Auto, 
78                    right: Val::Px(OUTER_MARGIN), 
79                    top: Val::Px(OUTER_MARGIN), 
80                    bottom: Val::Auto 
81                },
82                ..Default::default()
83            },
84            BackgroundColor(Color::srgb(0.2, 0.2, 0.2))
85        )).with_children(|c| {
86            c.spawn(Node {
87                flex_direction: FlexDirection::Column,
88                margin: UiRect { 
89                    left: Val::Px(INNER_MARGIN), 
90                    right: Val::Px(INNER_MARGIN), 
91                    top: Val::Px(INNER_MARGIN), 
92                    bottom: Val::Px(INNER_MARGIN) 
93                },
94                align_items: AlignItems::FlexStart,
95                ..Default::default()
96            }).with_children(|c| {
97                result = [
98                    c.spawn((Node::default(), font.clone(), Text::new("FPS:"))).id(),
99                    c.spawn((Node::default(), font.clone(), Text::new("Max Frametime:"))).id(),
100                    c.spawn((Node::default(), font.clone(), Text::new("Entities:"))).id(),
101                    c.spawn((Node::default(), font.clone(), Text::new("CPU Usage:"))).id(),
102                    c.spawn((Node::default(), font.clone(), Text::new("Memory Usage:"))).id(),
103                ];
104            });
105            c.spawn(Node {
106                flex_direction: FlexDirection::Column,
107                margin: UiRect { 
108                    left: Val::Px(INNER_MARGIN), 
109                    right: Val::Px(INNER_MARGIN), 
110                    top: Val::Px(INNER_MARGIN), 
111                    bottom: Val::Px(INNER_MARGIN) 
112                },
113                min_width: Val::Px(80.),
114                align_items: AlignItems::FlexEnd,
115                ..Default::default()
116            }).with_children(|c| {
117                result = [
118                    c.spawn((Node::default(), font.clone(), Text::default())).id(),
119                    c.spawn((Node::default(), font.clone(), Text::default())).id(),
120                    c.spawn((Node::default(), font.clone(), Text::default())).id(),
121                    c.spawn((Node::default(), font.clone(), Text::default())).id(),
122                    c.spawn((Node::default(), font.clone(), Text::default())).id(),
123                ];
124            });
125        });
126        result
127    });
128
129    time_buffer.push(time.delta_secs());
130    *sys_timer += time.delta_secs();
131    if *sys_timer > 0.2 {
132        *sys_timer -= 0.2;
133        sys_info.refresh_memory();
134        sys_info.refresh_cpu_usage();
135    }
136    
137    *refresh_timer += time.delta_secs();
138    if *refresh_timer > 0.05 {
139        *refresh_timer -= 0.05;
140
141        if let Ok(mut text) = text.get_mut(fps) {
142            text.0.clear();
143            let fps = time_buffer.len() as f32 / time_buffer.iter().sum::<f32>();
144            let _ = write!(&mut text.0, "{:.0}", fps);
145        };
146        if let Ok(mut text) = text.get_mut(max_ft) {
147            text.0.clear();
148            let max_ft = time_buffer.iter().max_by(|a, b| 
149                a.partial_cmp(b).unwrap_or(::core::cmp::Ordering::Equal)
150            ).unwrap_or(&0.0);
151            let _ = write!(&mut text.0, "{:.2}ms", max_ft * 1000.);
152        };
153        if let Ok(mut text) = text.get_mut(entities) {
154            text.0.clear();
155            let _ = write!(&mut text.0, "{}", *entity_count);
156        };
157        if let Ok(mut text) = text.get_mut(cpu) {
158            let pct = sys_info.global_cpu_usage() as i32;
159            text.0.clear();
160            let _ = write!(&mut text.0, "{}%", pct);
161        };
162        if let Ok(mut text) = text.get_mut(ram) {
163            let pct = (sys_info.used_memory() * 100).checked_div(sys_info.total_memory()).unwrap_or(0);
164            text.0.clear();
165            let _ = write!(&mut text.0, "{}%", pct);
166        };
167    }
168}
169
170fn __plugin(app: &mut App) {
171    app.add_systems(First, __entity_count.pipe(__diagnostic_system));
172}
173
174__plugin
175    }};
176}