Skip to main content

godot_bevy/plugins/
debugger.rs

1//! Bevy Entity Debugger Plugin
2//!
3//! This plugin integrates with Godot's EditorDebuggerPlugin system to provide
4//! real-time inspection of Bevy entities and components in the Godot editor.
5
6use bevy_app::{App, Plugin, Update};
7use bevy_ecs::prelude::{Name, Resource, World};
8use bevy_ecs::world::EntityRef;
9use bevy_reflect::{PartialReflect, ReflectFromPtr, ReflectRef};
10use bevy_time::Time;
11use godot::classes::EngineDebugger;
12use godot::meta::ToGodot;
13use godot::prelude::{VarDictionary as Dictionary, *};
14
15use crate::interop::GodotNodeHandle;
16use crate::plugins::scene_tree::GodotChildOf;
17use bevy_ecs::reflect::AppTypeRegistry;
18
19/// Configuration for the debugger plugin
20#[derive(Resource)]
21pub struct DebuggerConfig {
22    /// Whether the debugger is enabled
23    pub enabled: bool,
24    /// How often to send entity updates (in seconds)
25    pub update_interval: f32,
26}
27
28impl Default for DebuggerConfig {
29    fn default() -> Self {
30        Self {
31            enabled: true,
32            update_interval: 0.5, // Update twice per second
33        }
34    }
35}
36
37/// Timer resource for the debugger
38#[derive(Resource, Default)]
39struct DebuggerTimer {
40    elapsed: f32,
41}
42
43/// Plugin that enables Bevy entity inspection in Godot's debugger
44#[derive(Default)]
45pub struct GodotDebuggerPlugin;
46
47impl Plugin for GodotDebuggerPlugin {
48    fn build(&self, app: &mut App) {
49        app.init_resource::<DebuggerConfig>()
50            .init_resource::<DebuggerTimer>()
51            .add_systems(Update, debugger_exclusive_system);
52    }
53}
54
55fn debugger_exclusive_system(world: &mut World) {
56    let config = world.get_resource::<DebuggerConfig>();
57    let enabled = config.map(|c| c.enabled).unwrap_or(false);
58    let update_interval = config.map(|c| c.update_interval).unwrap_or(0.5);
59
60    if !enabled {
61        return;
62    }
63
64    let delta = world
65        .get_resource::<Time>()
66        .map(|t| t.delta_secs())
67        .unwrap_or(0.0);
68
69    let should_send = {
70        let mut timer = world.get_resource_mut::<DebuggerTimer>();
71        if let Some(ref mut timer) = timer {
72            timer.elapsed += delta;
73            if timer.elapsed < update_interval {
74                false
75            } else {
76                timer.elapsed = 0.0;
77                true
78            }
79        } else {
80            false
81        }
82    };
83
84    if !should_send {
85        return;
86    }
87
88    if !EngineDebugger::singleton().is_active() {
89        return;
90    }
91
92    // Clone registry so we can release the borrow on world
93    let type_registry = world.get_resource::<AppTypeRegistry>().cloned();
94
95    let mut entities = VarArray::new();
96    let mut query = world.query::<EntityRef>();
97
98    for entity_ref in query.iter(world) {
99        let name = entity_ref
100            .get::<Name>()
101            .map(|n| n.as_str().to_string())
102            .unwrap_or_default();
103
104        let has_godot_node = entity_ref.get::<GodotNodeHandle>().is_some();
105
106        let parent_bits: i64 = entity_ref
107            .get::<GodotChildOf>()
108            .map(|child_of| child_of.get().to_bits() as i64)
109            .unwrap_or(-1);
110
111        // Build component data with reflection
112        let mut components = VarArray::new();
113        let archetype = entity_ref.archetype();
114
115        for component_id in archetype.components() {
116            let Some(component_info) = world.components().get_info(*component_id) else {
117                continue;
118            };
119
120            let mut component_dict = Dictionary::new();
121
122            // Try to get pretty type name from registry, fallback to extracting from full path
123            let (full_name, short_name) = if let Some(ref registry) = type_registry {
124                let registry = registry.read();
125                if let Some(type_id) = component_info.type_id() {
126                    if let Some(registration) = registry.get(type_id) {
127                        let type_info = registration.type_info();
128                        let table = type_info.type_path_table();
129                        (table.path().to_string(), table.short_path().to_string())
130                    } else {
131                        extract_short_name(component_info.name().to_string())
132                    }
133                } else {
134                    extract_short_name(component_info.name().to_string())
135                }
136            } else {
137                extract_short_name(component_info.name().to_string())
138            };
139
140            // Skip hierarchy components - already shown visually in the tree
141            if short_name == "GodotChildOf"
142                || short_name == "GodotChildren"
143                || full_name.contains("::GodotChildOf")
144                || full_name.contains("::GodotChildren")
145            {
146                continue;
147            }
148
149            component_dict.set("name", GString::from(&full_name));
150            component_dict.set("short_name", GString::from(&short_name));
151
152            // Try to get reflected value
153            if let Some(ref registry) = type_registry {
154                let registry = registry.read();
155                if let Some(type_id) = component_info.type_id()
156                    && let Some(registration) = registry.get(type_id)
157                    && let Some(reflect_from_ptr) = registration.data::<ReflectFromPtr>()
158                    && let Ok(ptr) = entity_ref.get_by_id(*component_id)
159                {
160                    // SAFETY: ptr is valid and type matches registration
161                    let reflected = unsafe { reflect_from_ptr.as_reflect(ptr) };
162                    let value_dict = reflect_to_dict(reflected);
163                    component_dict.set("value", value_dict);
164                }
165            }
166
167            components.push(&component_dict.to_variant());
168        }
169
170        let mut entry = VarArray::new();
171        entry.push(&Variant::from(entity_ref.id().to_bits() as i64));
172        entry.push(&Variant::from(GString::from(&name)));
173        entry.push(&Variant::from(has_godot_node));
174        entry.push(&Variant::from(parent_bits));
175        entry.push(&components.to_variant());
176
177        entities.push(&entry.to_variant());
178    }
179
180    let mut debugger = EngineDebugger::singleton();
181    let message: GString = "bevy:entities".into();
182    debugger.send_message(&message, &entities);
183}
184
185/// Extract a short type name from a full path (e.g., "foo::bar::Baz" -> "Baz")
186fn extract_short_name(full_name: String) -> (String, String) {
187    let short = if let Some(pos) = full_name.rfind("::") {
188        full_name[pos + 2..].to_string()
189    } else {
190        full_name.clone()
191    };
192    (full_name, short)
193}
194
195/// Convert a reflected value to a Godot Dictionary
196fn reflect_to_dict(value: &dyn PartialReflect) -> Dictionary {
197    let mut dict = Dictionary::new();
198
199    match value.reflect_ref() {
200        ReflectRef::Struct(s) => {
201            dict.set("type", "struct");
202            let mut fields = Dictionary::new();
203            for i in 0..s.field_len() {
204                if let Some(field_name) = s.name_at(i)
205                    && let Some(field_value) = s.field_at(i)
206                {
207                    fields.set(field_name, reflect_value_to_variant(field_value));
208                }
209            }
210            dict.set("fields", fields);
211        }
212        ReflectRef::TupleStruct(ts) => {
213            dict.set("type", "tuple_struct");
214            let mut fields = VarArray::new();
215            for i in 0..ts.field_len() {
216                if let Some(field_value) = ts.field(i) {
217                    fields.push(&reflect_value_to_variant(field_value));
218                }
219            }
220            dict.set("fields", fields);
221        }
222        ReflectRef::Tuple(t) => {
223            dict.set("type", "tuple");
224            let mut fields = VarArray::new();
225            for i in 0..t.field_len() {
226                if let Some(field_value) = t.field(i) {
227                    fields.push(&reflect_value_to_variant(field_value));
228                }
229            }
230            dict.set("fields", fields);
231        }
232        ReflectRef::List(l) => {
233            dict.set("type", "list");
234            let mut items = VarArray::new();
235            for i in 0..l.len() {
236                if let Some(item) = l.get(i) {
237                    items.push(&reflect_value_to_variant(item));
238                }
239            }
240            dict.set("items", items);
241        }
242        ReflectRef::Map(m) => {
243            dict.set("type", "map");
244            dict.set("len", m.len() as i64);
245        }
246        ReflectRef::Set(s) => {
247            dict.set("type", "set");
248            dict.set("len", s.len() as i64);
249        }
250        ReflectRef::Enum(e) => {
251            dict.set("type", "enum");
252            dict.set("variant", e.variant_name());
253            let mut fields = Dictionary::new();
254            for i in 0..e.field_len() {
255                let field_name = e
256                    .name_at(i)
257                    .map(|s| s.to_string())
258                    .unwrap_or_else(|| i.to_string());
259                if let Some(field_value) = e.field_at(i) {
260                    fields.set(field_name, reflect_value_to_variant(field_value));
261                }
262            }
263            if !fields.is_empty() {
264                dict.set("fields", fields);
265            }
266        }
267        ReflectRef::Array(a) => {
268            dict.set("type", "array");
269            let mut items = VarArray::new();
270            for i in 0..a.len() {
271                if let Some(item) = a.get(i) {
272                    items.push(&reflect_value_to_variant(item));
273                }
274            }
275            dict.set("items", items);
276        }
277        ReflectRef::Opaque(_) => {
278            dict.set("type", "opaque");
279            // Try to get debug representation
280            if let Some(debug_str) = value.try_as_reflect().map(|r| format!("{r:?}")) {
281                dict.set("debug", GString::from(&debug_str));
282            }
283        }
284    }
285
286    dict
287}
288
289/// Convert a reflected value to a simple Godot Variant for display
290fn reflect_value_to_variant(value: &dyn PartialReflect) -> Variant {
291    // Try common primitive types first
292    if let Some(v) = value.try_downcast_ref::<f32>() {
293        return Variant::from(*v);
294    }
295    if let Some(v) = value.try_downcast_ref::<f64>() {
296        return Variant::from(*v);
297    }
298    if let Some(v) = value.try_downcast_ref::<i32>() {
299        return Variant::from(*v);
300    }
301    if let Some(v) = value.try_downcast_ref::<i64>() {
302        return Variant::from(*v);
303    }
304    if let Some(v) = value.try_downcast_ref::<u32>() {
305        return Variant::from(*v as i64);
306    }
307    if let Some(v) = value.try_downcast_ref::<u64>() {
308        return Variant::from(*v as i64);
309    }
310    if let Some(v) = value.try_downcast_ref::<bool>() {
311        return Variant::from(*v);
312    }
313    if let Some(v) = value.try_downcast_ref::<String>() {
314        return Variant::from(GString::from(v));
315    }
316    if let Some(v) = value.try_downcast_ref::<&str>() {
317        return Variant::from(GString::from(*v));
318    }
319
320    // For complex types, recurse
321    reflect_to_dict(value).to_variant()
322}