Files
marathon/crates/app/src/debug_ui.rs
2026-02-07 14:11:12 +00:00

187 lines
6.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Debug UI overlay using egui
use bevy::prelude::*;
use bevy::ecs::message::MessageWriter;
use libmarathon::debug_ui::{EguiContexts, EguiPrimaryContextPass};
use libmarathon::networking::{
EntityLockRegistry,
GossipBridge,
NetworkedEntity,
NodeVectorClock,
};
use crate::cube::{CubeMarker, DeleteCubeEvent, SpawnCubeEvent};
pub struct DebugUiPlugin;
impl Plugin for DebugUiPlugin {
fn build(&self, app: &mut App) {
app.add_systems(EguiPrimaryContextPass, render_debug_ui);
}
}
/// Render the debug UI panel
fn render_debug_ui(
mut egui_ctx: EguiContexts,
node_clock: Option<Res<NodeVectorClock>>,
gossip_bridge: Option<Res<GossipBridge>>,
lock_registry: Option<Res<EntityLockRegistry>>,
cube_query: Query<(&Transform, &NetworkedEntity), With<CubeMarker>>,
mut spawn_events: MessageWriter<SpawnCubeEvent>,
mut delete_events: MessageWriter<DeleteCubeEvent>,
) -> Result {
let ctx: &egui::Context = egui_ctx.ctx_mut()?;
egui::Window::new("Debug Info")
.default_pos([10.0, 10.0])
.default_width(300.0)
.resizable(true)
.show(ctx, |ui| {
ui.heading("Network Status");
ui.separator();
// Node information
if let Some(clock) = &node_clock {
ui.label(format!("Node ID: {}", &clock.node_id.to_string()[..8]));
// Show the current node's clock value (timestamp)
let current_timestamp =
clock.clock.clocks.get(&clock.node_id).copied().unwrap_or(0);
ui.label(format!("Clock: {}", current_timestamp));
ui.label(format!("Known nodes: {}", clock.clock.clocks.len()));
} else {
ui.label("Node: Not initialized");
}
ui.add_space(5.0);
// Gossip bridge status
if let Some(bridge) = &gossip_bridge {
ui.label(format!(
"Bridge Node: {}",
&bridge.node_id().to_string()[..8]
));
ui.label("Status: Connected");
} else {
ui.label("Gossip: Not ready");
}
ui.add_space(10.0);
ui.heading("Cube State");
ui.separator();
// Cube information
match cube_query.iter().next() {
| Some((transform, networked)) => {
let pos = transform.translation;
ui.label(format!(
"Position: ({:.2}, {:.2}, {:.2})",
pos.x, pos.y, pos.z
));
let (axis, angle) = transform.rotation.to_axis_angle();
let angle_deg: f32 = angle.to_degrees();
ui.label(format!(
"Rotation: {:.2}° around ({:.2}, {:.2}, {:.2})",
angle_deg, axis.x, axis.y, axis.z
));
ui.label(format!(
"Scale: ({:.2}, {:.2}, {:.2})",
transform.scale.x, transform.scale.y, transform.scale.z
));
ui.add_space(5.0);
ui.label(format!(
"Network ID: {}",
&networked.network_id.to_string()[..8]
));
ui.label(format!(
"Owner: {}",
&networked.owner_node_id.to_string()[..8]
));
},
| None => {
ui.label("Cube: Not spawned yet");
},
}
ui.add_space(10.0);
ui.heading("Entity Controls");
ui.separator();
if ui.button(" Spawn Cube").clicked() {
spawn_events.write(SpawnCubeEvent {
position: Vec3::new(
rand::random::<f32>() * 4.0 - 2.0,
0.5,
rand::random::<f32>() * 4.0 - 2.0,
),
});
}
ui.label(format!("Total cubes: {}", cube_query.iter().count()));
// List all cubes with delete buttons
ui.add_space(5.0);
egui::ScrollArea::vertical()
.id_salt("cube_list")
.max_height(150.0)
.show(ui, |ui| {
for (_transform, networked) in cube_query.iter() {
ui.horizontal(|ui| {
ui.label(format!("Cube {:.8}...", networked.network_id));
if ui.small_button("🗑").clicked() {
delete_events.write(DeleteCubeEvent {
entity_id: networked.network_id,
});
}
});
}
});
ui.add_space(10.0);
ui.heading("Lock Status");
ui.separator();
if let (Some(lock_registry), Some(clock)) = (&lock_registry, &node_clock) {
let node_id = clock.node_id;
let locked_cubes = cube_query
.iter()
.filter(|(_, networked)| lock_registry.is_locked(networked.network_id, node_id))
.count();
ui.label(format!("Locked entities: {}", locked_cubes));
ui.add_space(5.0);
egui::ScrollArea::vertical()
.id_salt("lock_list")
.max_height(100.0)
.show(ui, |ui| {
for (_, networked) in cube_query.iter() {
let entity_id = networked.network_id;
if let Some(holder) = lock_registry.get_holder(entity_id, node_id) {
let is_ours = holder == node_id;
ui.horizontal(|ui| {
ui.label(format!("🔒 {:.8}...", entity_id));
ui.label(if is_ours { "(you)" } else { "(peer)" });
});
}
}
});
} else {
ui.label("Lock registry: Not ready");
}
ui.add_space(10.0);
ui.heading("Controls");
ui.separator();
ui.label("Left click: Select cube");
ui.label("Left drag: Move cube (XY)");
ui.label("Right drag: Rotate cube");
ui.label("Scroll: Move cube (Z)");
ui.label("ESC: Deselect");
});
Ok(())
}