#[cfg(debug_assertions)]
use crate::interop::BulkOperationsCache;
use crate::interop::node_markers::{Node2DMarker, Node3DMarker};
use crate::interop::{GodotAccess, GodotNodeHandle};
use crate::plugins::transforms::{IntoBevyTransform, IntoGodotTransform, IntoGodotTransform2D};
use bevy_ecs::change_detection::{DetectChanges, Ref};
use bevy_ecs::entity::Entity;
use bevy_ecs::query::{AnyOf, Changed};
#[cfg(debug_assertions)]
use bevy_ecs::system::NonSendMut;
use bevy_ecs::system::{Query, SystemChangeTick};
#[cfg(debug_assertions)]
use bevy_math::Quat;
use bevy_transform::components::Transform as BevyTransform;
#[cfg(debug_assertions)]
use godot::builtin::{PackedInt64Array, VarDictionary};
#[cfg(debug_assertions)]
use godot::classes::Object;
use godot::classes::{Node2D, Node3D};
#[cfg(debug_assertions)]
use godot::prelude::{Gd, ToGodot};
use super::change_filter::TransformSyncMetadata;
#[cfg(debug_assertions)]
#[tracing::instrument]
pub fn pre_update_godot_transforms(
entities: Query<(
Entity,
&mut BevyTransform,
&GodotNodeHandle,
&mut TransformSyncMetadata,
AnyOf<(&Node2DMarker, &Node3DMarker)>,
)>,
mut godot: GodotAccess,
bulk_ops_cache: NonSendMut<BulkOperationsCache>,
) {
if let Some(bulk_ops) = bulk_ops_cache.get() {
let _bulk_span = tracing::info_span!("using_bulk_read_optimization").entered();
pre_update_godot_transforms_bulk(entities, bulk_ops);
return;
}
pre_update_godot_transforms_individual(entities, &mut godot);
}
#[cfg(not(debug_assertions))]
#[tracing::instrument]
pub fn pre_update_godot_transforms(
entities: Query<(
Entity,
&mut BevyTransform,
&GodotNodeHandle,
&mut TransformSyncMetadata,
AnyOf<(&Node2DMarker, &Node3DMarker)>,
)>,
mut godot: GodotAccess,
) {
pre_update_godot_transforms_individual(entities, &mut godot);
}
#[cfg(debug_assertions)]
fn pre_update_godot_transforms_bulk(
mut entities: Query<(
Entity,
&mut BevyTransform,
&GodotNodeHandle,
&mut TransformSyncMetadata,
AnyOf<(&Node2DMarker, &Node3DMarker)>,
)>,
mut batch_singleton: Gd<Object>,
) {
let _span = tracing::info_span!("bulk_read_preparation").entered();
let entity_count = entities.iter().len();
let mut entities_3d: Vec<(Entity, i64)> = Vec::with_capacity(entity_count);
let mut entities_2d: Vec<(Entity, i64)> = Vec::with_capacity(entity_count);
for (entity, _, reference, _, (node2d, node3d)) in entities.iter() {
let instance_id = reference.instance_id().to_i64();
if node2d.is_some() {
entities_2d.push((entity, instance_id));
} else if node3d.is_some() {
entities_3d.push((entity, instance_id));
}
}
drop(_span);
if !entities_3d.is_empty() {
let _span = tracing::info_span!("bulk_read_3d", count = entities_3d.len()).entered();
let instance_ids: Vec<i64> = entities_3d.iter().map(|(_, id)| *id).collect();
let ids_packed = PackedInt64Array::from(instance_ids.as_slice());
let result = batch_singleton
.call("bulk_get_transforms_3d", &[ids_packed.to_variant()])
.to::<VarDictionary>();
if let (Some(positions), Some(rotations), Some(scales)) = (
result
.get("positions")
.map(|v| v.to::<godot::builtin::PackedVector3Array>()),
result
.get("rotations")
.map(|v| v.to::<godot::builtin::PackedVector4Array>()),
result
.get("scales")
.map(|v| v.to::<godot::builtin::PackedVector3Array>()),
) {
for (i, (entity, _)) in entities_3d.iter().enumerate() {
if let Ok((_, mut bevy_transform, _, mut metadata, _)) = entities.get_mut(*entity)
&& let (Some(pos), Some(rot), Some(scale)) =
(positions.get(i), rotations.get(i), scales.get(i))
{
let new_bevy_transform = BevyTransform {
translation: bevy_math::Vec3::new(pos.x, pos.y, pos.z),
rotation: Quat::from_xyzw(rot.x, rot.y, rot.z, rot.w),
scale: bevy_math::Vec3::new(scale.x, scale.y, scale.z),
};
if *bevy_transform != new_bevy_transform {
*bevy_transform = new_bevy_transform;
metadata.last_sync_tick = Some(bevy_transform.last_changed());
}
}
}
}
}
if !entities_2d.is_empty() {
let _span = tracing::info_span!("bulk_read_2d", count = entities_2d.len()).entered();
let instance_ids: Vec<i64> = entities_2d.iter().map(|(_, id)| *id).collect();
let ids_packed = PackedInt64Array::from(instance_ids.as_slice());
let result = batch_singleton
.call("bulk_get_transforms_2d", &[ids_packed.to_variant()])
.to::<VarDictionary>();
if let (Some(positions), Some(rotations), Some(scales)) = (
result
.get("positions")
.map(|v| v.to::<godot::builtin::PackedVector2Array>()),
result
.get("rotations")
.map(|v| v.to::<godot::builtin::PackedFloat32Array>()),
result
.get("scales")
.map(|v| v.to::<godot::builtin::PackedVector2Array>()),
) {
for (i, (entity, _)) in entities_2d.iter().enumerate() {
if let Ok((_, mut bevy_transform, _, mut metadata, _)) = entities.get_mut(*entity)
&& let (Some(pos), Some(rot), Some(scale)) =
(positions.get(i), rotations.get(i), scales.get(i))
{
let new_bevy_transform = BevyTransform {
translation: bevy_math::Vec3::new(pos.x, pos.y, 0.0),
rotation: Quat::from_rotation_z(rot),
scale: bevy_math::Vec3::new(scale.x, scale.y, 1.0),
};
if *bevy_transform != new_bevy_transform {
*bevy_transform = new_bevy_transform;
metadata.last_sync_tick = Some(bevy_transform.last_changed());
}
}
}
}
}
}
fn pre_update_godot_transforms_individual(
mut entities: Query<(
Entity,
&mut BevyTransform,
&GodotNodeHandle,
&mut TransformSyncMetadata,
AnyOf<(&Node2DMarker, &Node3DMarker)>,
)>,
godot: &mut GodotAccess,
) {
for (_, mut bevy_transform, reference, mut metadata, (node2d, node3d)) in entities.iter_mut() {
let new_bevy_transform = if node2d.is_some() {
godot
.get::<Node2D>(*reference)
.get_transform()
.to_bevy_transform()
} else if node3d.is_some() {
godot
.get::<Node3D>(*reference)
.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());
}
}
}
#[cfg(debug_assertions)]
#[tracing::instrument]
pub fn post_update_godot_transforms(
change_tick: SystemChangeTick,
entities: Query<
(
Ref<BevyTransform>,
&GodotNodeHandle,
&TransformSyncMetadata,
AnyOf<(&Node2DMarker, &Node3DMarker)>,
),
Changed<BevyTransform>,
>,
mut godot: GodotAccess,
bulk_ops_cache: NonSendMut<BulkOperationsCache>,
) {
if let Some(bulk_ops) = bulk_ops_cache.get() {
let _bulk_span = tracing::info_span!("using_bulk_optimization").entered();
post_update_godot_transforms_bulk(change_tick, entities, bulk_ops);
return;
}
post_update_godot_transforms_individual(change_tick, entities, &mut godot);
}
#[cfg(not(debug_assertions))]
#[tracing::instrument]
pub fn post_update_godot_transforms(
change_tick: SystemChangeTick,
entities: Query<
(
Ref<BevyTransform>,
&GodotNodeHandle,
&TransformSyncMetadata,
AnyOf<(&Node2DMarker, &Node3DMarker)>,
),
Changed<BevyTransform>,
>,
mut godot: GodotAccess,
) {
post_update_godot_transforms_individual(change_tick, entities, &mut godot);
}
#[cfg(debug_assertions)]
fn post_update_godot_transforms_bulk(
change_tick: SystemChangeTick,
mut entities: Query<
(
Ref<BevyTransform>,
&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,
));
rotations_3d.push(godot::prelude::Vector4 {
x: transform_ref.rotation.x,
y: transform_ref.rotation.y,
z: transform_ref.rotation.z,
w: transform_ref.rotation.w,
});
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::PackedVector4Array::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>,
&GodotNodeHandle,
&TransformSyncMetadata,
AnyOf<(&Node2DMarker, &Node3DMarker)>,
),
Changed<BevyTransform>,
>,
godot: &mut GodotAccess,
) {
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;
}
if node2d.is_some() {
let _span = tracing::info_span!("individual_ffi_call_2d").entered();
let mut obj = godot.get::<Node2D>(*reference);
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 = godot.get::<Node3D>(*reference);
obj.set_transform(transform_ref.to_godot_transform());
}
}
}