1#![doc = include_str!("../README.md")]
2
3#[doc(hidden)]
4pub use sysinfo;
5
6#[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}