1use flight_entity::allocate_entity;
8use flight_log::{
9 LogLevel, LogSink, LogStateRef, add_log_sink, clear_log_channel_level, create_console_log_sink,
10 get_log_channel_level, get_log_level, remove_log_sink, set_log_channel_level, set_log_level,
11};
12use flight_render::{RenderState, enable_color_adjustment_guards, enable_render_registries_guards};
13use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
14
15use crate::types::*;
16
17pub fn create_debug_state() -> DebugState {
21 let mut out: DebugState = allocate_entity();
22 initialize_debug_state(&mut out);
23 return out;
24}
25
26pub fn initialize_debug_state(out: &mut DebugState) {
30 out.enabled = false;
31 out.enabled_subsystems = Vec::new();
32 out.frame_number = 0;
33 out.installed_sink = None;
34 out.saved_channel_levels = Vec::new();
35 out.saved_global_level = LogLevel::Verbose;
36 out.subsystems = Vec::new();
37}
38
39fn apply_debug_levels(log_state: &LogStateRef, level: LogLevel, channels: &[String]) {
41 set_log_level(log_state, level);
42 for channel in channels {
43 set_log_channel_level(log_state, channel, level);
44 }
45}
46
47fn collect_debug_channels(
49 subsystems: &[DebugSubsystemHooks],
50 extra: Option<&[String]>,
51) -> Vec<String> {
52 let mut channels = Vec::new();
53 for hooks in subsystems {
54 if let Some(own) = &hooks.channels {
55 channels.extend(own.iter().cloned());
56 }
57 }
58 if let Some(extra) = extra {
59 channels.extend(extra.iter().cloned());
60 }
61 return channels;
62}
63
64pub fn disable_debug(state: &DebugStateRef, log_state: &LogStateRef) {
69 if !state.read().unwrap().enabled {
70 return;
71 }
72 let enabled = state.read().unwrap().enabled_subsystems.clone();
73 for hooks in &enabled {
74 if let Some(disable_guards) = &hooks.disable_guards {
75 disable_guards();
76 }
77 }
78 state.write().unwrap().enabled_subsystems.clear();
79 remove_debug_sink(state, log_state);
80 restore_debug_levels(state, log_state);
81 state.write().unwrap().enabled = false;
82}
83
84pub fn enable_debug(
97 state: &DebugStateRef,
98 log_state: &LogStateRef,
99 options: Option<&DebugOptions>,
100) {
101 if state.read().unwrap().enabled {
102 return;
103 }
104 let level = options
105 .and_then(|options| options.level)
106 .unwrap_or(LogLevel::Debug);
107 let subsystems = resolve_debug_subsystems(
108 state,
109 options.and_then(|options| options.subsystems.as_deref()),
110 );
111 let channels = collect_debug_channels(
112 &subsystems,
113 options.and_then(|options| options.channels.as_deref()),
114 );
115 let saved_global_level = get_log_level(log_state);
116 state.write().unwrap().saved_global_level = saved_global_level;
117 let mut saved_channel_levels: Vec<(String, Option<LogLevel>)> = Vec::new();
118 for channel in &channels {
119 if !saved_channel_levels
120 .iter()
121 .any(|(saved, _)| saved == channel)
122 {
123 saved_channel_levels.push((channel.clone(), get_log_channel_level(log_state, channel)));
124 }
125 }
126 let sink = options
127 .and_then(|options| options.sink.clone())
128 .unwrap_or_else(|| create_console_log_sink(log_state, None));
129 let enabled = catch_unwind(AssertUnwindSafe(|| {
130 apply_debug_levels(log_state, level, &channels);
131 install_debug_sink(state, log_state, &sink);
132 for hooks in &subsystems {
133 state
134 .write()
135 .unwrap()
136 .enabled_subsystems
137 .push(hooks.clone());
138 if let Some(enable_guards) = &hooks.enable_guards {
139 enable_guards();
140 }
141 }
142 }));
143 if let Err(error) = enabled {
144 let switched_on = std::mem::take(&mut state.write().unwrap().enabled_subsystems);
145 for hooks in switched_on.iter().rev() {
146 if let Some(disable_guards) = &hooks.disable_guards {
147 let _ = catch_unwind(AssertUnwindSafe(|| disable_guards()));
149 }
150 }
151 let _ = catch_unwind(AssertUnwindSafe(|| remove_debug_sink(state, log_state)));
152 for (channel, saved_level) in &saved_channel_levels {
153 let _ = catch_unwind(AssertUnwindSafe(|| match saved_level {
154 None => clear_log_channel_level(log_state, channel),
155 Some(saved_level) => set_log_channel_level(log_state, channel, *saved_level),
156 }));
157 }
158 let _ = catch_unwind(AssertUnwindSafe(|| {
159 set_log_level(log_state, saved_global_level)
160 }));
161 resume_unwind(error);
162 }
163 let mut current = state.write().unwrap();
164 current.saved_channel_levels = saved_channel_levels;
165 current.enabled = true;
166}
167
168pub fn enable_flight_diagnostics<S: RenderState>(
172 debug_state: &DebugStateRef,
173 log_state: &LogStateRef,
174 state: &mut S,
175) {
176 enable_debug(debug_state, log_state, None);
177 enable_color_adjustment_guards(state, Some(log_state.clone()));
178 enable_render_registries_guards(state, Some(log_state.clone()));
179}
180
181fn install_debug_sink(state: &DebugStateRef, log_state: &LogStateRef, sink: &LogSink) {
183 state.write().unwrap().installed_sink = Some(sink.clone());
184 add_log_sink(log_state, sink.clone());
185}
186
187pub fn is_debug_enabled(state: &DebugStateRef) -> bool {
190 return state.read().unwrap().enabled;
191}
192
193pub fn register_debug_subsystem(state: &DebugStateRef, name: &str, hooks: &DebugSubsystemHooks) {
197 let mut current = state.write().unwrap();
198 if let Some(entry) = current
199 .subsystems
200 .iter_mut()
201 .find(|(registered, _)| registered == name)
202 {
203 entry.1 = hooks.clone();
204 return;
205 }
206 current.subsystems.push((name.to_string(), hooks.clone()));
207}
208
209fn remove_debug_sink(state: &DebugStateRef, log_state: &LogStateRef) {
211 let installed = state.write().unwrap().installed_sink.take();
212 if let Some(sink) = installed {
213 remove_log_sink(log_state, &sink);
214 }
215}
216
217fn resolve_debug_subsystems(
220 state: &DebugStateRef,
221 names: Option<&[DebugSubsystemName]>,
222) -> Vec<DebugSubsystemHooks> {
223 let current = state.read().unwrap();
224 let Some(names) = names else {
225 return current
226 .subsystems
227 .iter()
228 .map(|(_, hooks)| hooks.clone())
229 .collect();
230 };
231 let mut resolved = Vec::new();
232 for name in names {
233 if let Some((_, hooks)) = current
234 .subsystems
235 .iter()
236 .find(|(registered, _)| registered == name)
237 {
238 resolved.push(hooks.clone());
239 }
240 }
241 return resolved;
242}
243
244fn restore_debug_levels(state: &DebugStateRef, log_state: &LogStateRef) {
247 let (saved_global_level, saved_channel_levels) = {
248 let mut current = state.write().unwrap();
249 (
250 current.saved_global_level,
251 std::mem::take(&mut current.saved_channel_levels),
252 )
253 };
254 set_log_level(log_state, saved_global_level);
255 for (channel, saved_level) in &saved_channel_levels {
256 match saved_level {
257 None => clear_log_channel_level(log_state, channel),
258 Some(saved_level) => set_log_channel_level(log_state, channel, *saved_level),
259 }
260 }
261}
262
263pub fn unregister_debug_subsystem(state: &DebugStateRef, name: &str) -> bool {
267 let mut current = state.write().unwrap();
268 let Some(index) = current
269 .subsystems
270 .iter()
271 .position(|(registered, _)| registered == name)
272 else {
273 return false;
274 };
275 current.subsystems.remove(index);
276 return true;
277}