use crate::interop::GodotNodeHandle;
use crate::interop::node_markers::{Node2DMarker, Node3DMarker};
use crate::plugins::transforms::{IntoBevyTransform, IntoGodotTransform, IntoGodotTransform2D};
use crate::prelude::main_thread_system;
use bevy::ecs::change_detection::{DetectChanges, Ref};
use bevy::ecs::query::{AnyOf, Changed};
use bevy::ecs::system::{Query, SystemChangeTick};
use bevy::prelude::Transform as BevyTransform;
use godot::classes::{Engine, Node2D, Node3D, Object, SceneTree};
use godot::prelude::{Gd, ToGodot};
use super::change_filter::TransformSyncMetadata;
#[main_thread_system]
#[tracing::instrument]
pub fn pre_update_godot_transforms(
mut entities: Query<(
&mut BevyTransform,
&mut GodotNodeHandle,
&mut TransformSyncMetadata,
AnyOf<(&Node2DMarker, &Node3DMarker)>,
)>,
) {
for (mut bevy_transform, mut reference, mut metadata, (node2d, node3d)) in entities.iter_mut() {
let new_bevy_transform = if node2d.is_some() {
reference
.get::<Node2D>()
.get_transform()
.to_bevy_transform()
} else if node3d.is_some() {
reference
.get::<Node3D>()
.get_transform()
.to_bevy_transform()
} else {
panic!("Expected AnyOf to match either a Node2D or a Node3D, is there a bug in bevy?");
};
if *bevy_transform != new_bevy_transform {
*bevy_transform = new_bevy_transform;
metadata.last_sync_tick = Some(bevy_transform.last_changed());
}
}
}
#[main_thread_system]
#[tracing::instrument]
pub fn post_update_godot_transforms(
change_tick: SystemChangeTick,
entities: Query<
(
Ref<BevyTransform>,
&mut GodotNodeHandle,
&TransformSyncMetadata,
AnyOf<(&Node2DMarker, &Node3DMarker)>,
),
Changed<BevyTransform>,
>,
) {
let engine = Engine::singleton();
if let Some(scene_tree) = engine
.get_main_loop()
.and_then(|main_loop| main_loop.try_cast::<SceneTree>().ok())
&& let Some(root) = scene_tree.get_root()
&& let Some(bevy_app) = root.get_node_or_null("BevyAppSingleton")
{
if bevy_app.has_method("bulk_update_transforms_3d") {
let _bulk_span = tracing::info_span!("using_bulk_optimization").entered();
post_update_godot_transforms_bulk(change_tick, entities, bevy_app.upcast::<Object>());
return;
}
}
post_update_godot_transforms_individual(change_tick, entities);
}
fn post_update_godot_transforms_bulk(
change_tick: SystemChangeTick,
mut entities: Query<
(
Ref<BevyTransform>,
&mut GodotNodeHandle,
&TransformSyncMetadata,
AnyOf<(&Node2DMarker, &Node3DMarker)>,
),
Changed<BevyTransform>,
>,
mut batch_singleton: Gd<Object>,
) {
let _span = tracing::info_span!("bulk_data_preparation_optimized").entered();
let entity_count = entities.iter().count();
let mut instance_ids_3d = Vec::with_capacity(entity_count);
let mut positions_3d = Vec::with_capacity(entity_count);
let mut rotations_3d = Vec::with_capacity(entity_count);
let mut scales_3d = Vec::with_capacity(entity_count);
let mut instance_ids_2d = Vec::with_capacity(entity_count);
let mut positions_2d = Vec::with_capacity(entity_count);
let mut rotations_2d = Vec::with_capacity(entity_count);
let mut scales_2d = Vec::with_capacity(entity_count);
let _collect_span = tracing::info_span!("collect_raw_arrays").entered();
for (transform_ref, reference, metadata, (node2d, node3d)) in entities.iter_mut() {
if let Some(sync_tick) = metadata.last_sync_tick
&& !transform_ref
.last_changed()
.is_newer_than(sync_tick, change_tick.this_run())
{
continue;
}
let instance_id = reference.instance_id();
if node2d.is_some() {
instance_ids_2d.push(instance_id.to_i64());
positions_2d.push(godot::prelude::Vector2::new(
transform_ref.translation.x,
transform_ref.translation.y,
));
let (_, _, z) = transform_ref.rotation.to_euler(bevy::math::EulerRot::XYZ);
rotations_2d.push(z);
scales_2d.push(godot::prelude::Vector2::new(
transform_ref.scale.x,
transform_ref.scale.y,
));
} else if node3d.is_some() {
instance_ids_3d.push(instance_id.to_i64());
positions_3d.push(godot::prelude::Vector3::new(
transform_ref.translation.x,
transform_ref.translation.y,
transform_ref.translation.z,
));
let (x, y, z) = transform_ref.rotation.to_euler(bevy::math::EulerRot::XYZ);
rotations_3d.push(godot::prelude::Vector3::new(x, y, z));
scales_3d.push(godot::prelude::Vector3::new(
transform_ref.scale.x,
transform_ref.scale.y,
transform_ref.scale.z,
));
}
}
drop(_collect_span);
let has_3d_updates = !instance_ids_3d.is_empty();
let has_2d_updates = !instance_ids_2d.is_empty();
drop(_span);
let total_updates = instance_ids_3d.len() + instance_ids_2d.len();
if total_updates > 0 {
let _ffi_calls_span =
tracing::info_span!("raw_array_ffi_calls", total_entities = total_updates).entered();
if has_3d_updates {
let _span =
tracing::info_span!("raw_ffi_call_3d", entities = instance_ids_3d.len()).entered();
let instance_ids_packed =
godot::prelude::PackedInt64Array::from(instance_ids_3d.as_slice());
let positions_packed =
godot::prelude::PackedVector3Array::from(positions_3d.as_slice());
let rotations_packed =
godot::prelude::PackedVector3Array::from(rotations_3d.as_slice());
let scales_packed = godot::prelude::PackedVector3Array::from(scales_3d.as_slice());
batch_singleton.call(
"bulk_update_transforms_3d",
&[
instance_ids_packed.to_variant(),
positions_packed.to_variant(),
rotations_packed.to_variant(),
scales_packed.to_variant(),
],
);
}
if has_2d_updates {
let _span =
tracing::info_span!("raw_ffi_call_2d", entities = instance_ids_2d.len()).entered();
let instance_ids_packed =
godot::prelude::PackedInt64Array::from(instance_ids_2d.as_slice());
let positions_packed =
godot::prelude::PackedVector2Array::from(positions_2d.as_slice());
let rotations_packed =
godot::prelude::PackedFloat32Array::from(rotations_2d.as_slice());
let scales_packed = godot::prelude::PackedVector2Array::from(scales_2d.as_slice());
batch_singleton.call(
"bulk_update_transforms_2d",
&[
instance_ids_packed.to_variant(),
positions_packed.to_variant(),
rotations_packed.to_variant(),
scales_packed.to_variant(),
],
);
}
}
}
fn post_update_godot_transforms_individual(
change_tick: SystemChangeTick,
mut entities: Query<
(
Ref<BevyTransform>,
&mut GodotNodeHandle,
&TransformSyncMetadata,
AnyOf<(&Node2DMarker, &Node3DMarker)>,
),
Changed<BevyTransform>,
>,
) {
for (transform_ref, mut reference, metadata, (node2d, node3d)) in entities.iter_mut() {
if let Some(sync_tick) = metadata.last_sync_tick
&& !transform_ref
.last_changed()
.is_newer_than(sync_tick, change_tick.this_run())
{
continue;
}
if node2d.is_some() {
let _span = tracing::info_span!("individual_ffi_call_2d").entered();
let mut obj = reference.get::<Node2D>();
obj.set_transform(transform_ref.to_godot_transform_2d());
} else if node3d.is_some() {
let _span = tracing::info_span!("individual_ffi_call_3d").entered();
let mut obj = reference.get::<Node3D>();
obj.set_transform(transform_ref.to_godot_transform());
}
}
}