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