use flight_entity::allocate_entity;
use flight_log::{
LogLevel, LogSink, LogStateRef, add_log_sink, clear_log_channel_level, create_console_log_sink,
get_log_channel_level, get_log_level, remove_log_sink, set_log_channel_level, set_log_level,
};
use flight_render::{RenderState, enable_color_adjustment_guards, enable_render_registries_guards};
use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
use crate::types::*;
pub fn create_debug_state() -> DebugState {
let mut out: DebugState = allocate_entity();
initialize_debug_state(&mut out);
return out;
}
pub fn initialize_debug_state(out: &mut DebugState) {
out.enabled = false;
out.enabled_subsystems = Vec::new();
out.frame_number = 0;
out.installed_sink = None;
out.saved_channel_levels = Vec::new();
out.saved_global_level = LogLevel::Verbose;
out.subsystems = Vec::new();
}
fn apply_debug_levels(log_state: &LogStateRef, level: LogLevel, channels: &[String]) {
set_log_level(log_state, level);
for channel in channels {
set_log_channel_level(log_state, channel, level);
}
}
fn collect_debug_channels(
subsystems: &[DebugSubsystemHooks],
extra: Option<&[String]>,
) -> Vec<String> {
let mut channels = Vec::new();
for hooks in subsystems {
if let Some(own) = &hooks.channels {
channels.extend(own.iter().cloned());
}
}
if let Some(extra) = extra {
channels.extend(extra.iter().cloned());
}
return channels;
}
pub fn disable_debug(state: &DebugStateRef, log_state: &LogStateRef) {
if !state.read().unwrap().enabled {
return;
}
let enabled = state.read().unwrap().enabled_subsystems.clone();
for hooks in &enabled {
if let Some(disable_guards) = &hooks.disable_guards {
disable_guards();
}
}
state.write().unwrap().enabled_subsystems.clear();
remove_debug_sink(state, log_state);
restore_debug_levels(state, log_state);
state.write().unwrap().enabled = false;
}
pub fn enable_debug(
state: &DebugStateRef,
log_state: &LogStateRef,
options: Option<&DebugOptions>,
) {
if state.read().unwrap().enabled {
return;
}
let level = options
.and_then(|options| options.level)
.unwrap_or(LogLevel::Debug);
let subsystems = resolve_debug_subsystems(
state,
options.and_then(|options| options.subsystems.as_deref()),
);
let channels = collect_debug_channels(
&subsystems,
options.and_then(|options| options.channels.as_deref()),
);
let saved_global_level = get_log_level(log_state);
state.write().unwrap().saved_global_level = saved_global_level;
let mut saved_channel_levels: Vec<(String, Option<LogLevel>)> = Vec::new();
for channel in &channels {
if !saved_channel_levels
.iter()
.any(|(saved, _)| saved == channel)
{
saved_channel_levels.push((channel.clone(), get_log_channel_level(log_state, channel)));
}
}
let sink = options
.and_then(|options| options.sink.clone())
.unwrap_or_else(|| create_console_log_sink(log_state, None));
let enabled = catch_unwind(AssertUnwindSafe(|| {
apply_debug_levels(log_state, level, &channels);
install_debug_sink(state, log_state, &sink);
for hooks in &subsystems {
state
.write()
.unwrap()
.enabled_subsystems
.push(hooks.clone());
if let Some(enable_guards) = &hooks.enable_guards {
enable_guards();
}
}
}));
if let Err(error) = enabled {
let switched_on = std::mem::take(&mut state.write().unwrap().enabled_subsystems);
for hooks in switched_on.iter().rev() {
if let Some(disable_guards) = &hooks.disable_guards {
let _ = catch_unwind(AssertUnwindSafe(|| disable_guards()));
}
}
let _ = catch_unwind(AssertUnwindSafe(|| remove_debug_sink(state, log_state)));
for (channel, saved_level) in &saved_channel_levels {
let _ = catch_unwind(AssertUnwindSafe(|| match saved_level {
None => clear_log_channel_level(log_state, channel),
Some(saved_level) => set_log_channel_level(log_state, channel, *saved_level),
}));
}
let _ = catch_unwind(AssertUnwindSafe(|| {
set_log_level(log_state, saved_global_level)
}));
resume_unwind(error);
}
let mut current = state.write().unwrap();
current.saved_channel_levels = saved_channel_levels;
current.enabled = true;
}
pub fn enable_flight_diagnostics<S: RenderState>(
debug_state: &DebugStateRef,
log_state: &LogStateRef,
state: &mut S,
) {
enable_debug(debug_state, log_state, None);
enable_color_adjustment_guards(state, Some(log_state.clone()));
enable_render_registries_guards(state, Some(log_state.clone()));
}
fn install_debug_sink(state: &DebugStateRef, log_state: &LogStateRef, sink: &LogSink) {
state.write().unwrap().installed_sink = Some(sink.clone());
add_log_sink(log_state, sink.clone());
}
pub fn is_debug_enabled(state: &DebugStateRef) -> bool {
return state.read().unwrap().enabled;
}
pub fn register_debug_subsystem(state: &DebugStateRef, name: &str, hooks: &DebugSubsystemHooks) {
let mut current = state.write().unwrap();
if let Some(entry) = current
.subsystems
.iter_mut()
.find(|(registered, _)| registered == name)
{
entry.1 = hooks.clone();
return;
}
current.subsystems.push((name.to_string(), hooks.clone()));
}
fn remove_debug_sink(state: &DebugStateRef, log_state: &LogStateRef) {
let installed = state.write().unwrap().installed_sink.take();
if let Some(sink) = installed {
remove_log_sink(log_state, &sink);
}
}
fn resolve_debug_subsystems(
state: &DebugStateRef,
names: Option<&[DebugSubsystemName]>,
) -> Vec<DebugSubsystemHooks> {
let current = state.read().unwrap();
let Some(names) = names else {
return current
.subsystems
.iter()
.map(|(_, hooks)| hooks.clone())
.collect();
};
let mut resolved = Vec::new();
for name in names {
if let Some((_, hooks)) = current
.subsystems
.iter()
.find(|(registered, _)| registered == name)
{
resolved.push(hooks.clone());
}
}
return resolved;
}
fn restore_debug_levels(state: &DebugStateRef, log_state: &LogStateRef) {
let (saved_global_level, saved_channel_levels) = {
let mut current = state.write().unwrap();
(
current.saved_global_level,
std::mem::take(&mut current.saved_channel_levels),
)
};
set_log_level(log_state, saved_global_level);
for (channel, saved_level) in &saved_channel_levels {
match saved_level {
None => clear_log_channel_level(log_state, channel),
Some(saved_level) => set_log_channel_level(log_state, channel, *saved_level),
}
}
}
pub fn unregister_debug_subsystem(state: &DebugStateRef, name: &str) -> bool {
let mut current = state.write().unwrap();
let Some(index) = current
.subsystems
.iter()
.position(|(registered, _)| registered == name)
else {
return false;
};
current.subsystems.remove(index);
return true;
}