1use super::node_type_checking::{
2 add_node_type_markers_from_string, remove_comprehensive_node_type_markers,
3};
4use crate::plugins::core::SceneTreeComponentRegistry;
5use crate::prelude::GodotScene;
6use crate::{
7 interop::{GodotAccess, GodotNodeHandle},
8 plugins::collisions::{
9 AREA_ENTERED, AREA_EXITED, BODY_ENTERED, BODY_EXITED, CollisionMessageType,
10 },
11};
12use bevy_app::{App, First, Plugin, PreStartup};
13use bevy_ecs::{
14 component::Component,
15 entity::Entity,
16 message::{Message, MessageReader, MessageWriter, message_update_system},
17 prelude::{Name, ReflectComponent, ReflectResource, Resource},
18 schedule::IntoScheduleConfigs,
19 system::{Commands, NonSendMut, Query, Res, ResMut, SystemParam},
20};
21use bevy_reflect::Reflect;
22use godot::classes::ClassDb;
23use godot::{
24 builtin::{GString, StringName},
25 classes::{Engine, Node, SceneTree},
26 meta::ToGodot,
27 obj::{Gd, Inherits, InstanceId, Singleton},
28 prelude::GodotConvert,
29};
30use parking_lot::Mutex;
31use std::collections::HashMap;
32use std::marker::PhantomData;
33use tracing::{debug, trace, warn};
34
35#[derive(Resource, Default, Debug, Reflect)]
55#[reflect(Resource)]
56pub struct NodeEntityIndex {
57 #[reflect(ignore)]
58 index: HashMap<InstanceId, Entity>,
59}
60
61impl NodeEntityIndex {
62 #[inline]
66 pub fn get(&self, instance_id: InstanceId) -> Option<Entity> {
67 self.index.get(&instance_id).copied()
68 }
69
70 #[inline]
72 pub fn contains(&self, instance_id: InstanceId) -> bool {
73 self.index.contains_key(&instance_id)
74 }
75
76 #[inline]
78 pub fn get_handle(&self, handle: GodotNodeHandle) -> Option<Entity> {
79 self.get(handle.instance_id())
80 }
81
82 #[inline]
84 pub fn contains_handle(&self, handle: GodotNodeHandle) -> bool {
85 self.contains(handle.instance_id())
86 }
87
88 #[inline]
90 pub fn len(&self) -> usize {
91 self.index.len()
92 }
93
94 #[inline]
96 pub fn is_empty(&self) -> bool {
97 self.index.is_empty()
98 }
99
100 #[inline]
104 pub(crate) fn insert(&mut self, instance_id: InstanceId, entity: Entity) {
105 self.index.insert(instance_id, entity);
106 }
107
108 #[inline]
112 pub(crate) fn remove(&mut self, instance_id: InstanceId) -> Option<Entity> {
113 self.index.remove(&instance_id)
114 }
115}
116
117pub struct GodotSceneTreePlugin {
126 pub auto_despawn_children: bool,
134}
135
136impl Default for GodotSceneTreePlugin {
137 fn default() -> Self {
138 Self {
139 auto_despawn_children: true,
140 }
141 }
142}
143
144#[derive(Resource, Reflect)]
146#[reflect(Resource)]
147pub struct SceneTreeConfig {
148 pub auto_despawn_children: bool,
156}
157
158impl Plugin for GodotSceneTreePlugin {
159 fn build(&self, app: &mut App) {
160 super::autosync::register_all_autosync_bundles(app);
162
163 app.init_non_send_resource::<SceneTreeRefImpl>()
164 .init_resource::<NodeEntityIndex>()
165 .insert_resource(SceneTreeConfig {
166 auto_despawn_children: self.auto_despawn_children,
167 })
168 .add_message::<SceneTreeMessage>()
169 .add_systems(
170 PreStartup,
171 (connect_scene_tree, initialize_scene_tree).chain(),
172 )
173 .add_systems(
174 First,
175 (
176 write_scene_tree_messages.before(message_update_system),
177 read_scene_tree_messages.before(message_update_system),
178 ),
179 );
180 }
181}
182
183#[derive(SystemParam)]
184pub struct SceneTreeRef<'w, 's> {
185 gd: NonSendMut<'w, SceneTreeRefImpl>,
186 phantom: PhantomData<&'s ()>,
187}
188
189impl<'w, 's> SceneTreeRef<'w, 's> {
190 pub fn get(&mut self) -> Gd<SceneTree> {
191 self.gd.0.clone()
192 }
193}
194
195#[doc(hidden)]
196#[derive(Debug)]
197pub(crate) struct SceneTreeRefImpl(Gd<SceneTree>);
198
199impl SceneTreeRefImpl {
200 fn get_ref() -> Gd<SceneTree> {
201 Engine::singleton()
202 .get_main_loop()
203 .unwrap()
204 .cast::<SceneTree>()
205 }
206}
207
208impl Default for SceneTreeRefImpl {
209 fn default() -> Self {
210 Self(Self::get_ref())
211 }
212}
213
214fn initialize_scene_tree(
215 mut commands: Commands,
216 mut scene_tree: SceneTreeRef,
217 mut entities: Query<(&GodotNodeHandle, Entity, Option<&ProtectedNodeEntity>)>,
218 component_registry: Res<SceneTreeComponentRegistry>,
219 mut node_index: ResMut<NodeEntityIndex>,
220 mut godot: GodotAccess,
221) {
222 let root = scene_tree.get().get_root().unwrap();
223
224 let optimized_watcher = root
226 .try_get_node_as::<Node>("/root/BevyAppSingleton/OptimizedSceneTreeWatcher")
227 .or_else(|| root.try_get_node_as::<Node>("BevyAppSingleton/OptimizedSceneTreeWatcher"));
228
229 let messages = if let Some(mut watcher) = optimized_watcher {
230 tracing::info!("Using optimized initial tree analysis with type pre-analysis");
232
233 let analysis_result = watcher.call("analyze_initial_tree", &[]);
234 let result_dict = analysis_result.to::<godot::builtin::VarDictionary>();
235 let instance_ids = result_dict
236 .get("instance_ids")
237 .unwrap()
238 .to::<godot::builtin::PackedInt64Array>();
239 let node_types = result_dict
240 .get("node_types")
241 .unwrap()
242 .to::<godot::builtin::PackedStringArray>();
243 let node_names = result_dict
244 .get("node_names")
245 .map(|value| value.to::<godot::builtin::PackedStringArray>());
246 let parent_ids = result_dict
247 .get("parent_ids")
248 .map(|value| value.to::<godot::builtin::PackedInt64Array>());
249 let collision_masks = result_dict
250 .get("collision_masks")
251 .map(|value| value.to::<godot::builtin::PackedInt64Array>());
252 let groups_array = result_dict
254 .get("groups")
255 .map(|value| value.to::<godot::builtin::VarArray>());
256
257 let mut messages = Vec::new();
258 let len = instance_ids.len().min(node_types.len());
259 for i in 0..len {
260 if let (Some(id), Some(type_gstring)) = (instance_ids.get(i), node_types.get(i)) {
261 let type_str = type_gstring.to_string();
262 let node_name = node_names
263 .as_ref()
264 .and_then(|names| names.get(i))
265 .map(|name| name.to_string());
266 let parent_id =
267 parent_ids
268 .as_ref()
269 .and_then(|ids| ids.get(i))
270 .and_then(|parent_id| {
271 if parent_id > 0 {
272 Some(InstanceId::from_i64(parent_id))
273 } else {
274 None
275 }
276 });
277 let collision_mask = collision_masks
278 .as_ref()
279 .and_then(|masks| masks.get(i))
280 .and_then(|mask| u8::try_from(mask).ok());
281 let groups = groups_array.as_ref().and_then(|arr| {
283 arr.get(i).map(|variant| {
284 let packed = variant.to::<godot::builtin::PackedStringArray>();
285 packed
286 .as_slice()
287 .iter()
288 .map(|s| s.to_string())
289 .collect::<Vec<_>>()
290 })
291 });
292
293 messages.push(SceneTreeMessage {
294 node_id: GodotNodeHandle::from(godot::prelude::InstanceId::from_i64(id)),
295 message_type: SceneTreeMessageType::NodeAdded,
296 node_type: Some(type_str),
297 node_name,
298 parent_id,
299 collision_mask,
300 groups,
301 });
302 }
303 }
304
305 messages
306 } else {
307 tracing::info!("Using fallback initial tree analysis (no type optimization)");
309 traverse_fallback(root.upcast())
310 };
311
312 create_scene_tree_entity(
313 &mut commands,
314 messages,
315 &mut scene_tree,
316 &mut entities,
317 &component_registry,
318 &mut node_index,
319 &mut godot,
320 );
321}
322
323fn traverse_fallback(node: Gd<Node>) -> Vec<SceneTreeMessage> {
324 fn traverse_recursive(node: Gd<Node>, messages: &mut Vec<SceneTreeMessage>) {
325 messages.push(SceneTreeMessage {
326 node_id: GodotNodeHandle::from(node.instance_id()),
327 message_type: SceneTreeMessageType::NodeAdded,
328 node_type: None, node_name: None,
330 parent_id: None,
331 collision_mask: None,
332 groups: None, });
334
335 for child in node.get_children().iter_shared() {
336 traverse_recursive(child, messages);
337 }
338 }
339
340 let mut messages = Vec::new();
341 traverse_recursive(node, &mut messages);
342 messages
343}
344
345#[derive(Debug, Clone, Message)]
346pub struct SceneTreeMessage {
347 pub node_id: GodotNodeHandle,
348 pub message_type: SceneTreeMessageType,
349 pub node_type: Option<String>, pub node_name: Option<String>,
351 pub parent_id: Option<InstanceId>,
352 pub collision_mask: Option<u8>,
353 pub groups: Option<Vec<String>>, }
355
356#[derive(Copy, Clone, Debug, GodotConvert)]
357#[godot(via = GString)]
358pub enum SceneTreeMessageType {
359 NodeAdded,
360 NodeRemoved,
361 NodeRenamed,
362}
363
364const COLLISION_MASK_BODY_ENTERED: u8 = 1 << 0;
365const COLLISION_MASK_BODY_EXITED: u8 = 1 << 1;
366const COLLISION_MASK_AREA_ENTERED: u8 = 1 << 2;
367const COLLISION_MASK_AREA_EXITED: u8 = 1 << 3;
368
369fn collision_mask_from_node(node: &mut Node) -> u8 {
370 let mut mask = 0;
371 if node.has_signal(BODY_ENTERED) {
372 mask |= COLLISION_MASK_BODY_ENTERED;
373 }
374 if node.has_signal(BODY_EXITED) {
375 mask |= COLLISION_MASK_BODY_EXITED;
376 }
377 if node.has_signal(AREA_ENTERED) {
378 mask |= COLLISION_MASK_AREA_ENTERED;
379 }
380 if node.has_signal(AREA_EXITED) {
381 mask |= COLLISION_MASK_AREA_EXITED;
382 }
383 mask
384}
385
386fn collision_mask_has(mask: u8, flag: u8) -> bool {
387 mask & flag != 0
388}
389
390fn find_node_by_name(parent: &Gd<Node>, name: &StringName) -> Option<Gd<Node>> {
392 if &parent.get_name() == name {
394 return Some(parent.clone());
395 }
396
397 for i in 0..parent.get_child_count() {
399 if let Some(child) = parent.get_child(i) {
400 let child_node = child.cast::<Node>();
401 if let Some(found) = find_node_by_name(&child_node, name) {
402 return Some(found);
403 }
404 }
405 }
406
407 None
408}
409
410fn connect_scene_tree(mut scene_tree: SceneTreeRef) {
411 let mut scene_tree_gd = scene_tree.get();
412 let root = scene_tree_gd.get_root().unwrap();
413
414 let watcher = root
416 .try_get_node_as::<Node>("/root/BevyAppSingleton/SceneTreeWatcher")
417 .or_else(|| {
418 root.try_get_node_as::<Node>("BevyAppSingleton/SceneTreeWatcher")
420 })
421 .or_else(|| {
422 tracing::debug!("Searching entire scene tree for SceneTreeWatcher");
424 find_node_by_name(&root.clone().upcast(), &StringName::from("SceneTreeWatcher"))
425 })
426 .unwrap_or_else(|| {
427 panic!("SceneTreeWatcher not found. Searched /root/BevyAppSingleton/SceneTreeWatcher, BevyAppSingleton/SceneTreeWatcher, and entire tree.");
428 });
429
430 let optimized_watcher = root
432 .try_get_node_as::<Node>("/root/BevyAppSingleton/OptimizedSceneTreeWatcher")
433 .or_else(|| root.try_get_node_as::<Node>("BevyAppSingleton/OptimizedSceneTreeWatcher"))
434 .or_else(|| {
435 find_node_by_name(
437 &root.clone().upcast(),
438 &StringName::from("OptimizedSceneTreeWatcher"),
439 )
440 });
441
442 if optimized_watcher.is_some() {
443 tracing::info!("Using optimized GDScript scene tree watcher with type pre-analysis");
447 } else {
448 tracing::info!("Using fallback scene tree connection (no type optimization)");
450
451 scene_tree_gd.connect(
452 "node_added",
453 &watcher
454 .callable("scene_tree_event")
455 .bind(&[SceneTreeMessageType::NodeAdded.to_variant()]),
456 );
457
458 scene_tree_gd.connect(
459 "node_removed",
460 &watcher
461 .callable("scene_tree_event")
462 .bind(&[SceneTreeMessageType::NodeRemoved.to_variant()]),
463 );
464
465 scene_tree_gd.connect(
466 "node_renamed",
467 &watcher
468 .callable("scene_tree_event")
469 .bind(&[SceneTreeMessageType::NodeRenamed.to_variant()]),
470 );
471 }
472}
473
474#[derive(Component, Debug, Reflect)]
475#[reflect(Component)]
476pub struct Groups {
477 groups: Vec<String>,
478}
479
480impl Groups {
481 pub fn is(&self, group_name: &str) -> bool {
482 self.groups.iter().any(|name| name == group_name)
483 }
484}
485
486impl<T: Inherits<Node>> From<&Gd<T>> for Groups {
487 fn from(node: &Gd<T>) -> Self {
488 Groups {
489 groups: node
490 .clone()
491 .upcast::<Node>()
492 .get_groups()
493 .iter_shared()
494 .map(|variant| variant.to_string())
495 .collect(),
496 }
497 }
498}
499
500impl From<Vec<String>> for Groups {
501 fn from(groups: Vec<String>) -> Self {
502 Groups { groups }
503 }
504}
505
506#[derive(Resource)]
509pub struct SceneTreeMessageReader(pub Mutex<crossbeam_channel::Receiver<SceneTreeMessage>>);
510
511impl SceneTreeMessageReader {
512 pub fn new(receiver: crossbeam_channel::Receiver<SceneTreeMessage>) -> Self {
513 Self(Mutex::new(receiver))
514 }
515}
516
517fn write_scene_tree_messages(
518 message_reader: Res<SceneTreeMessageReader>,
519 mut message_writer: MessageWriter<SceneTreeMessage>,
520) {
521 let receiver = message_reader.0.lock();
522 let messages: Vec<_> = receiver.try_iter().collect();
523 message_writer.write_batch(messages);
524}
525
526#[derive(Component)]
531pub struct ProtectedNodeEntity;
532
533fn create_scene_tree_entity(
534 commands: &mut Commands,
535 messages: impl IntoIterator<Item = SceneTreeMessage>,
536 scene_tree: &mut SceneTreeRef,
537 entities: &mut Query<(&GodotNodeHandle, Entity, Option<&ProtectedNodeEntity>)>,
538 component_registry: &SceneTreeComponentRegistry,
539 node_index: &mut NodeEntityIndex,
540 godot: &mut GodotAccess,
541) {
542 let mut godot_entity_map = entities
544 .iter()
545 .map(|(reference, ent, protected)| (reference.instance_id(), (ent, protected)))
546 .collect::<HashMap<_, _>>();
547 let scene_root = scene_tree.get().get_root().unwrap();
548
549 let collision_watcher = scene_root
551 .try_get_node_as::<Node>("/root/BevyAppSingleton/CollisionWatcher")
552 .or_else(|| {
553 scene_root.try_get_node_as::<Node>("BevyAppSingleton/CollisionWatcher")
555 })
556 .or_else(|| {
557 tracing::debug!("Searching entire scene tree for CollisionWatcher");
559 find_node_by_name(
560 &scene_root.clone().upcast(),
561 &StringName::from("CollisionWatcher"),
562 )
563 });
564
565 let mut pending_collision_bodies: Vec<(i64, u8)> = Vec::new();
568
569 for message in messages.into_iter() {
570 trace!(target: "godot_scene_tree_messages", message = ?message);
571
572 let SceneTreeMessage {
573 node_id,
574 message_type,
575 node_type,
576 node_name,
577 parent_id: parent_id_from_gdscript,
578 collision_mask,
579 groups,
580 } = message;
581 let instance_id = node_id.instance_id();
582 let node_handle = node_id;
583 let entity_info = godot_entity_map.get(&instance_id).cloned();
584
585 match message_type {
586 SceneTreeMessageType::NodeAdded => {
587 if !instance_id.lookup_validity() {
589 continue;
590 }
591
592 let mut new_entity_commands = if let Some((ent, _)) = entity_info {
593 commands.entity(ent)
594 } else {
595 commands.spawn_empty()
596 };
597
598 let mut node_accessor = godot.node(node_handle);
599 let mut node = node_accessor.get::<Node>();
600
601 let node_name = node_name.unwrap_or_else(|| node.get_name().to_string());
602 new_entity_commands
603 .insert(node_id)
604 .insert(Name::from(node_name));
605
606 for class_name in get_inheritance_hierarchy(
608 node_type
609 .unwrap_or_else(|| node.get_class().to_string())
611 .as_str(),
612 ) {
613 add_node_type_markers_from_string(
614 &mut new_entity_commands,
615 class_name.as_str(),
616 );
617 }
618
619 let collision_mask = collision_mask.or_else(|| {
623 collision_watcher
624 .as_ref()
625 .map(|_| collision_mask_from_node(&mut node))
626 });
627
628 if collision_watcher.is_some()
630 && let Some(mask) = collision_mask
631 {
632 let is_collision_body = collision_mask_has(mask, COLLISION_MASK_BODY_ENTERED)
633 || collision_mask_has(mask, COLLISION_MASK_AREA_ENTERED);
634
635 if is_collision_body {
636 debug!(target: "godot_scene_tree_collisions",
637 node_id = instance_id.to_string(),
638 "is collision body");
639
640 pending_collision_bodies.push((instance_id.to_i64(), mask));
642 }
643 }
644 if let Some(groups_vec) = groups {
646 new_entity_commands.insert(Groups::from(groups_vec));
647 } else {
648 new_entity_commands.insert(Groups::from(&node));
649 }
650
651 component_registry.add_to_entity(&mut new_entity_commands, &mut node_accessor);
653
654 let new_entity = new_entity_commands.id();
655 godot_entity_map.insert(
656 instance_id,
657 (new_entity, entity_info.and_then(|(_, protected)| protected)),
658 );
659 node_index.insert(instance_id, new_entity);
660
661 super::autosync::try_add_bundles_for_node(commands, new_entity, godot, node_handle);
663
664 let parent_id = parent_id_from_gdscript
666 .or_else(|| node.get_parent().map(|parent| parent.instance_id()));
667 if let Some(parent_id) = parent_id
668 && parent_id != scene_root.instance_id()
669 {
670 if let Some((parent_entity, _)) = godot_entity_map.get(&parent_id) {
671 commands
672 .entity(new_entity)
673 .insert(super::relationship::GodotChildOf(*parent_entity));
674 } else {
675 warn!(target: "godot_scene_tree_messages",
676 "Parent entity with ID {} not found in godot_entity_map. This might indicate a missing or incorrect mapping. Path={}",
677 parent_id, node.get_path());
678 }
679 }
680 }
681 SceneTreeMessageType::NodeRemoved => {
682 if let Some((ent, prot_opt)) = entity_info {
683 let is_reparenting = godot
688 .try_get::<Node>(node_handle)
689 .map(|godot_node| godot_node.get_parent().is_some())
690 .unwrap_or(false);
691
692 if is_reparenting {
693 trace!(target: "godot_scene_tree_events",
695 "Node is being reparented, preserving entity");
696 } else {
698 let protected = prot_opt.is_some();
700 if !protected {
701 commands.entity(ent).despawn();
702 } else {
703 _strip_godot_components(commands, ent);
704 }
705 godot_entity_map.remove(&instance_id);
706 node_index.remove(instance_id);
707 }
708 } else {
709 trace!(target: "godot_scene_tree_messages", "Entity for removed node was already despawned");
711 }
712 }
713 SceneTreeMessageType::NodeRenamed => {
714 if let Some((ent, _)) = entity_info {
715 let name = node_name
716 .unwrap_or_else(|| godot.get::<Node>(node_handle).get_name().to_string());
717 commands.entity(ent).insert(Name::from(name));
718 } else {
719 trace!(target: "godot_scene_tree_messages", "Entity for renamed node was already despawned");
720 }
721 }
722 }
723 }
724
725 if !pending_collision_bodies.is_empty()
727 && let Some(ref collision_watcher) = collision_watcher
728 {
729 batch_connect_collision_signals(&scene_root, collision_watcher, &pending_collision_bodies);
730 }
731}
732
733fn get_inheritance_hierarchy(class_name: &str) -> Vec<String> {
734 let class_db = ClassDb::singleton();
735 let mut hierarchy = Vec::new();
736
737 let mut current_class = StringName::from(class_name);
739
740 while !current_class.is_empty() {
741 hierarchy.push(current_class.to_string());
743
744 current_class = class_db.get_parent_class(¤t_class);
746 }
747
748 hierarchy
749}
750
751fn batch_connect_collision_signals(
754 scene_root: &Gd<godot::classes::Window>,
755 collision_watcher: &Gd<Node>,
756 pending_bodies: &[(i64, u8)],
757) {
758 use godot::builtin::PackedInt64Array;
759
760 let bulk_ops = scene_root
762 .get_node_or_null("BevyAppSingleton/OptimizedBulkOperations")
763 .or_else(|| scene_root.get_node_or_null("/root/BevyAppSingleton/OptimizedBulkOperations"))
764 .filter(|node| node.has_method("bulk_connect_collision_signals"));
765
766 if let Some(mut bulk_ops) = bulk_ops {
767 let instance_ids: Vec<i64> = pending_bodies.iter().map(|(id, _)| *id).collect();
769 let collision_masks: Vec<i64> = pending_bodies
770 .iter()
771 .map(|(_, mask)| i64::from(*mask))
772 .collect();
773
774 let ids_packed = PackedInt64Array::from(instance_ids.as_slice());
775 let masks_packed = PackedInt64Array::from(collision_masks.as_slice());
776
777 bulk_ops.call(
778 "bulk_connect_collision_signals",
779 &[
780 ids_packed.to_variant(),
781 masks_packed.to_variant(),
782 collision_watcher.to_variant(),
783 ],
784 );
785 } else {
786 for (instance_id, mask) in pending_bodies {
788 let instance_id = InstanceId::from_i64(*instance_id);
789 if !instance_id.lookup_validity() {
790 continue;
791 }
792
793 let Some(mut node) = Gd::<Node>::try_from_instance_id(instance_id).ok() else {
795 continue;
796 };
797
798 let node_clone = node.clone();
799
800 if collision_mask_has(*mask, COLLISION_MASK_BODY_ENTERED) {
801 node.connect(
802 BODY_ENTERED,
803 &collision_watcher.callable("collision_event").bind(&[
804 node_clone.to_variant(),
805 CollisionMessageType::Started.to_variant(),
806 ]),
807 );
808 }
809
810 if collision_mask_has(*mask, COLLISION_MASK_BODY_EXITED) {
811 node.connect(
812 BODY_EXITED,
813 &collision_watcher.callable("collision_event").bind(&[
814 node_clone.to_variant(),
815 CollisionMessageType::Ended.to_variant(),
816 ]),
817 );
818 }
819
820 if collision_mask_has(*mask, COLLISION_MASK_AREA_ENTERED) {
821 node.connect(
822 AREA_ENTERED,
823 &collision_watcher.callable("collision_event").bind(&[
824 node_clone.to_variant(),
825 CollisionMessageType::Started.to_variant(),
826 ]),
827 );
828 }
829
830 if collision_mask_has(*mask, COLLISION_MASK_AREA_EXITED) {
831 node.connect(
832 AREA_EXITED,
833 &collision_watcher.callable("collision_event").bind(&[
834 node_clone.to_variant(),
835 CollisionMessageType::Ended.to_variant(),
836 ]),
837 );
838 }
839 }
840
841 debug!(target: "godot_scene_tree_collisions",
842 count = pending_bodies.len(),
843 "Individually connected collision signals (bulk ops not available)");
844 }
845}
846
847fn _strip_godot_components(commands: &mut Commands, ent: Entity) {
848 let mut entity_commands = commands.entity(ent);
849
850 entity_commands.remove::<GodotNodeHandle>();
851 entity_commands.remove::<GodotScene>();
852 entity_commands.remove::<Name>();
853 entity_commands.remove::<Groups>();
854
855 remove_comprehensive_node_type_markers(&mut entity_commands);
856}
857
858fn try_process_node_renamed_messages_fast_path(
859 commands: &mut Commands,
860 messages: &[SceneTreeMessage],
861 node_index: &NodeEntityIndex,
862 godot: &mut GodotAccess,
863) -> bool {
864 if !messages
865 .iter()
866 .all(|message| matches!(message.message_type, SceneTreeMessageType::NodeRenamed))
867 {
868 return false;
869 }
870
871 for message in messages {
872 let node_handle = message.node_id;
873 let Some(entity) = node_index.get(node_handle.instance_id()) else {
874 trace!(target: "godot_scene_tree_messages", "Entity for renamed node was already despawned");
875 continue;
876 };
877
878 let name = message
879 .node_name
880 .clone()
881 .unwrap_or_else(|| godot.get::<Node>(node_handle).get_name().to_string());
882 commands.entity(entity).insert(Name::from(name));
883 }
884
885 true
886}
887
888#[allow(clippy::too_many_arguments)]
889fn read_scene_tree_messages(
890 mut commands: Commands,
891 mut scene_tree: SceneTreeRef,
892 mut message_reader: MessageReader<SceneTreeMessage>,
893 mut entities: Query<(&GodotNodeHandle, Entity, Option<&ProtectedNodeEntity>)>,
894 component_registry: Res<SceneTreeComponentRegistry>,
895 mut node_index: ResMut<NodeEntityIndex>,
896 mut godot: GodotAccess,
897) {
898 let messages: Vec<_> = message_reader.read().cloned().collect();
899 if messages.is_empty() {
900 return;
901 }
902
903 if try_process_node_renamed_messages_fast_path(
904 &mut commands,
905 &messages,
906 &node_index,
907 &mut godot,
908 ) {
909 return;
910 }
911
912 create_scene_tree_entity(
913 &mut commands,
914 messages,
915 &mut scene_tree,
916 &mut entities,
917 &component_registry,
918 &mut node_index,
919 &mut godot,
920 );
921}