1use core::sync::atomic::{AtomicBool, Ordering};
2use ringbuf::traits::{Consumer, Observer, Producer, Split};
3
4#[cfg(not(feature = "std"))]
5use bevy_platform::prelude::String;
6
7use crate::collector::ArcGc;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub struct RealtimeLoggerConfig {
13 pub max_message_length: usize,
20
21 pub num_slots: usize,
26}
27
28impl Default for RealtimeLoggerConfig {
29 fn default() -> Self {
30 Self {
31 max_message_length: 128,
32 num_slots: 32,
33 }
34 }
35}
36
37pub fn realtime_logger(config: RealtimeLoggerConfig) -> (RealtimeLogger, RealtimeLoggerMainThread) {
38 #[cfg(debug_assertions)]
39 let (mut debug_prod_1, debug_cons_1) = ringbuf::HeapRb::new(config.num_slots).split();
40 #[cfg(debug_assertions)]
41 let (debug_prod_2, debug_cons_2) = ringbuf::HeapRb::new(config.num_slots).split();
42
43 let (mut error_prod_1, error_cons_1) = ringbuf::HeapRb::new(config.num_slots).split();
44 let (error_prod_2, error_cons_2) = ringbuf::HeapRb::new(config.num_slots).split();
45
46 #[cfg(debug_assertions)]
47 for _ in 0..config.num_slots {
48 let mut slot = String::new();
49 slot.reserve_exact(config.max_message_length);
50
51 debug_prod_1.try_push(slot).unwrap();
52 }
53
54 for _ in 0..config.num_slots {
55 let mut slot = String::new();
56 slot.reserve_exact(config.max_message_length);
57
58 error_prod_1.try_push(slot).unwrap();
59 }
60
61 let shared_state = ArcGc::new(SharedState {
62 message_too_long_occured: AtomicBool::new(false),
63 not_enough_slots_occured: AtomicBool::new(false),
64 });
65
66 (
67 RealtimeLogger {
68 #[cfg(debug_assertions)]
69 debug_prod: debug_prod_2,
70 #[cfg(debug_assertions)]
71 debug_cons: debug_cons_1,
72 error_prod: error_prod_2,
73 error_cons: error_cons_1,
74 shared_state: ArcGc::clone(&shared_state),
75 max_msg_length: config.max_message_length,
76 },
77 RealtimeLoggerMainThread {
78 #[cfg(debug_assertions)]
79 debug_prod: debug_prod_1,
80 #[cfg(debug_assertions)]
81 debug_cons: debug_cons_2,
82 error_prod: error_prod_1,
83 error_cons: error_cons_2,
84 shared_state,
85 },
86 )
87}
88
89struct SharedState {
90 message_too_long_occured: AtomicBool,
91 not_enough_slots_occured: AtomicBool,
92}
93
94pub struct RealtimeLogger {
96 #[cfg(debug_assertions)]
97 debug_prod: ringbuf::HeapProd<String>,
98 #[cfg(debug_assertions)]
99 debug_cons: ringbuf::HeapCons<String>,
100
101 error_prod: ringbuf::HeapProd<String>,
102 error_cons: ringbuf::HeapCons<String>,
103
104 shared_state: ArcGc<SharedState>,
105
106 max_msg_length: usize,
107}
108
109impl RealtimeLogger {
110 pub fn max_message_length(&self) -> usize {
112 self.max_msg_length
113 }
114
115 pub fn available_debug_slots(&self) -> usize {
119 #[cfg(debug_assertions)]
120 return self.debug_cons.occupied_len();
121 #[cfg(not(debug_assertions))]
122 return 0;
123 }
124
125 pub fn available_error_slots(&self) -> usize {
127 self.error_cons.occupied_len()
128 }
129
130 #[allow(unused)]
137 pub fn try_debug(&mut self, message: &str) -> Result<(), RealtimeLogError> {
138 #[cfg(debug_assertions)]
139 {
140 if message.len() > self.max_msg_length {
141 self.shared_state
142 .message_too_long_occured
143 .store(true, Ordering::Relaxed);
144 return Err(RealtimeLogError::MessageTooLong);
145 }
146
147 let Some(mut slot) = self.debug_cons.try_pop() else {
148 self.shared_state
149 .not_enough_slots_occured
150 .store(true, Ordering::Relaxed);
151 return Err(RealtimeLogError::OutOfSlots);
152 };
153
154 slot.clear();
155 slot.push_str(message);
156
157 let _ = self.debug_prod.try_push(slot);
158
159 return Ok(());
160 }
161
162 #[cfg(not(debug_assertions))]
163 return Ok(());
164 }
165
166 #[allow(unused)]
176 pub fn try_debug_with(&mut self, f: impl FnOnce(&mut String)) -> Result<(), RealtimeLogError> {
177 #[cfg(debug_assertions)]
178 {
179 let Some(mut slot) = self.debug_cons.try_pop() else {
180 self.shared_state
181 .not_enough_slots_occured
182 .store(true, Ordering::Relaxed);
183 return Err(RealtimeLogError::OutOfSlots);
184 };
185
186 slot.clear();
187
188 (f)(&mut slot);
189
190 let _ = self.debug_prod.try_push(slot);
191
192 return Ok(());
193 }
194
195 #[cfg(not(debug_assertions))]
196 return Ok(());
197 }
198
199 pub fn try_error(&mut self, message: &str) -> Result<(), RealtimeLogError> {
201 if message.len() > self.max_msg_length {
202 self.shared_state
203 .message_too_long_occured
204 .store(true, Ordering::Relaxed);
205 return Err(RealtimeLogError::MessageTooLong);
206 }
207
208 let Some(mut slot) = self.error_cons.try_pop() else {
209 self.shared_state
210 .not_enough_slots_occured
211 .store(true, Ordering::Relaxed);
212 return Err(RealtimeLogError::OutOfSlots);
213 };
214
215 slot.clear();
216 slot.push_str(message);
217
218 let _ = self.error_prod.try_push(slot);
219
220 Ok(())
221 }
222
223 pub fn try_error_with(&mut self, f: impl FnOnce(&mut String)) -> Result<(), RealtimeLogError> {
228 let Some(mut slot) = self.error_cons.try_pop() else {
229 self.shared_state
230 .not_enough_slots_occured
231 .store(true, Ordering::Relaxed);
232 return Err(RealtimeLogError::OutOfSlots);
233 };
234
235 slot.clear();
236
237 (f)(&mut slot);
238
239 let _ = self.error_prod.try_push(slot);
240
241 Ok(())
242 }
243}
244
245pub struct RealtimeLoggerMainThread {
247 #[cfg(debug_assertions)]
248 debug_prod: ringbuf::HeapProd<String>,
249 #[cfg(debug_assertions)]
250 debug_cons: ringbuf::HeapCons<String>,
251
252 error_prod: ringbuf::HeapProd<String>,
253 error_cons: ringbuf::HeapCons<String>,
254
255 shared_state: ArcGc<SharedState>,
256}
257
258impl RealtimeLoggerMainThread {
259 pub fn flush(&mut self) {
261 if self
262 .shared_state
263 .message_too_long_occured
264 .swap(false, Ordering::Relaxed)
265 {
266 log::error!("One or more realtime log messages were dropped because they were too long. Please increase message capacity.");
267 }
268 if self
269 .shared_state
270 .not_enough_slots_occured
271 .swap(false, Ordering::Relaxed)
272 {
273 log::error!("One or more realtime log messages were dropped because the realtime logger ran out of slots. Please increase slot capacity.");
274 }
275
276 #[cfg(debug_assertions)]
277 for slot in self.debug_cons.pop_iter() {
278 log::debug!("{}", slot.as_str());
279 let _ = self.debug_prod.try_push(slot).unwrap();
280 }
281
282 for slot in self.error_cons.pop_iter() {
283 log::error!("{}", slot.as_str());
284 let _ = self.error_prod.try_push(slot).unwrap();
285 }
286 }
287}
288
289#[derive(Debug, Clone, Copy, thiserror::Error)]
290pub enum RealtimeLogError {
291 #[error("There is not enough space to fit the message in the realtime log buffer")]
293 MessageTooLong,
294 #[error("The realtime log buffer is out of slots")]
295 OutOfSlots,
296}