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