use bevy::{
app::{Plugin, Startup, Update},
ecs::{
archetype::Archetypes,
component::Components,
entity::Entity,
error::Result,
query::Without,
resource::Resource,
schedule::IntoScheduleConfigs,
system::{Commands, Local, Query, Res, ResMut, SystemState},
world::World,
},
input::{common_conditions::input_just_pressed, keyboard::KeyCode},
log::{
self, Level,
tracing::{
field::{Field, ValueSet, Visit},
span,
},
tracing_subscriber::{Layer, Registry},
},
};
use bevy_egui::{
EguiContext, EguiContexts, EguiPrimaryContextPass, PrimaryEguiContext,
egui::{self, Grid, ScrollArea, Widget},
};
use crate::{EXTERNAL_SYSTEMS_CMDS, spawn_egui_camera};
pub struct DebugPlugin;
#[derive(Clone, Debug)]
pub struct LogEvent {
file: Option<String>,
line: Option<u32>,
module_path: Option<String>,
message: Option<String>,
other_fields: Vec<(String, String)>,
level: Level,
}
impl Visit for LogEvent {
fn record_str(&mut self, field: &Field, value: &str) {
match field.name() {
"message" => {
self.message = Some(value.to_owned());
}
_ => self.record_debug(field, &value),
}
}
fn record_debug(&mut self, field: &Field, value: &dyn core::fmt::Debug) {
match field.name() {
"message" => {
self.message = Some(format!("{:?}", value));
}
_ => {
self.other_fields
.push((field.name().to_owned(), format!("{:?}", value)));
}
}
}
}
#[derive(Resource, Default)]
pub struct DebugLogList(Vec<LogEvent>);
pub struct DebugLogLayer;
impl Layer<Registry> for DebugLogLayer {
fn on_event(
&self,
event: &bevy::log::tracing::Event<'_>,
ctx: bevy::log::tracing_subscriber::layer::Context<'_, Registry>,
) {
if !event.metadata().is_event() {
return;
}
let Some(cmds) = EXTERNAL_SYSTEMS_CMDS.get() else {
return;
};
let mut log_event = LogEvent {
file: event.metadata().file().map(|s| s.to_owned()),
line: event.metadata().line(),
other_fields: vec![],
message: None,
module_path: event.metadata().module_path().map(|s| s.to_owned()),
level: *event.metadata().level(),
};
event.record(&mut log_event);
cmds.send(Box::new(move |mut c| {
c.queue(move |world: &mut World| {
let mut list = world.resource_mut::<DebugLogList>();
list.0.push(log_event);
});
Ok(())
}))
.ok();
}
}
#[derive(Resource, Default)]
struct DebugUiState {
is_open: bool,
log_window_open: bool,
world_tree_view_open: bool,
}
impl Plugin for DebugPlugin {
fn build(&self, app: &mut bevy::app::App) {
app.add_systems(Startup, spawn_egui_camera)
.insert_resource(DebugUiState::default())
.insert_resource(DebugLogList::default())
.add_systems(
Update,
toggle_debug_system.run_if(input_just_pressed(KeyCode::Backslash)),
)
.add_systems(
EguiPrimaryContextPass,
(
debug_ui_log_window,
debug_ui_system_top_panel,
debug_ui_world_tree_view,
)
.run_if(|resource: Res<DebugUiState>| resource.into_inner().is_open),
);
}
}
fn toggle_debug_system(mut resource: ResMut<DebugUiState>) {
resource.is_open ^= true;
}
fn debug_ui_system_top_panel(
mut contexts: EguiContexts,
mut ui_state: ResMut<DebugUiState>,
) -> Result {
egui::TopBottomPanel::top("top_panel").show(contexts.ctx_mut()?, |ui| {
ui.menu_button("Widgets", |ui| {
ui.toggle_value(&mut ui_state.log_window_open, "Log window");
})
});
Ok(())
}
fn debug_ui_log_window(
mut contexts: EguiContexts,
mut ui_state: ResMut<DebugUiState>,
log_list: Res<DebugLogList>,
) -> Result {
egui::Window::new("Debug")
.open(&mut ui_state.log_window_open)
.show(contexts.ctx_mut()?, |ui| {
ScrollArea::vertical().show(ui, |ui| {
Grid::new("Log Grid")
.striped(true)
.num_columns(2)
.show(ui, |ui| {
for log in &log_list.0 {
log.ui(ui);
ui.end_row();
}
})
});
});
Ok(())
}
fn debug_ui_world_tree_view(
mut contexts: EguiContexts,
entities: Query<Entity, Without<EguiContext>>,
archetypes: &Archetypes,
components: &Components,
cmds: Commands,
mut ui_state: ResMut<DebugUiState>,
) -> Result {
egui::Window::new("World View")
.open(&mut ui_state.log_window_open)
.show(contexts.ctx_mut()?, |ui| {
ScrollArea::vertical().show(ui, |ui| {
Grid::new("World Grid")
.striped(true)
.num_columns(1)
.show(ui, |ui| {
for entity in entities {
let Some(archetype) = archetypes
.iter()
.find(|a| a.entities().iter().any(|v| v.id() == entity))
else {
continue;
};
let ent_ci = archetype.components();
for comp in ent_ci {
let Some(component_info) = components.get_info(*comp) else {
continue;
};
ui.label(&component_info.name().to_string());
}
ui.end_row();
}
})
});
});
Ok(())
}
impl Widget for &LogEvent {
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
ui.label(self.level.as_str());
let response = ui.label(
self.message
.as_ref()
.map(|v| v.as_str())
.unwrap_or("NO MESSAGE"),
);
response
}
}