#[macro_export]
macro_rules! add_transform_sync_systems {
($app:expr, $($tokens:tt)*) => {
$crate::add_transform_sync_systems!(@parse_all $app, $($tokens)*);
};
(@parse_all $app:expr, $name:ident = bevy_to_godot: $query:ty, $($rest:tt)*) => {
$crate::add_transform_sync_systems!(@generate_post_system $app, $name, $query);
$crate::add_transform_sync_systems!(@parse_all $app, $($rest)*);
};
(@parse_all $app:expr, $name:ident = godot_to_bevy: $query:ty, $($rest:tt)*) => {
$crate::add_transform_sync_systems!(@generate_pre_system $app, $name, $query);
$crate::add_transform_sync_systems!(@parse_all $app, $($rest)*);
};
(@parse_all $app:expr, $name:ident = $query:ty, $($rest:tt)*) => {
$crate::add_transform_sync_systems!(@generate_systems $app, $name, $query, $query);
$crate::add_transform_sync_systems!(@parse_all $app, $($rest)*);
};
(@parse_all $app:expr, $name:ident = bevy_to_godot: $query:ty) => {
$crate::add_transform_sync_systems!(@generate_post_system $app, $name, $query);
};
(@parse_all $app:expr, $name:ident = godot_to_bevy: $query:ty) => {
$crate::add_transform_sync_systems!(@generate_pre_system $app, $name, $query);
};
(@parse_all $app:expr, $name:ident = $query:ty) => {
$crate::add_transform_sync_systems!(@generate_systems $app, $name, $query, $query);
};
(@parse_all $app:expr,) => {};
(@parse_all $app:expr) => {};
(@generate_systems $app:expr, $name:ident, $bevy_to_godot_query:ty, $godot_to_bevy_query:ty) => {
$crate::add_transform_sync_systems!(@generate_post_system $app, $name, $bevy_to_godot_query);
$crate::add_transform_sync_systems!(@generate_pre_system $app, $name, $godot_to_bevy_query);
};
(@generate_post_system $app:expr, $name:ident, $bevy_to_godot_query:ty) => {
$crate::paste::paste! {
#[tracing::instrument]
#[$crate::prelude::main_thread_system]
pub fn [<post_update_godot_transforms_ $name:lower>](
change_tick: bevy::ecs::system::SystemChangeTick,
entities: bevy::prelude::Query<
(
bevy::ecs::change_detection::Ref<bevy::prelude::Transform>,
&mut $crate::interop::GodotNodeHandle,
&$crate::plugins::transforms::TransformSyncMetadata,
bevy::ecs::query::AnyOf<(&$crate::interop::node_markers::Node2DMarker, &$crate::interop::node_markers::Node3DMarker)>,
),
(
bevy::ecs::query::Changed<bevy::prelude::Transform>,
$bevy_to_godot_query,
),
>,
) {
use $crate::plugins::transforms::{IntoGodotTransform, IntoGodotTransform2D};
use bevy::ecs::change_detection::DetectChanges;
use godot::classes::{Engine, Node2D, Node3D, Object, SceneTree};
use godot::global::godot_print;
use godot::prelude::{Array, Dictionary, Gd, ToGodot};
let engine = Engine::singleton();
if let Some(scene_tree) = engine
.get_main_loop()
.and_then(|main_loop| main_loop.try_cast::<SceneTree>().ok())
{
if let Some(root) = scene_tree.get_root() {
if let Some(bevy_app) = root.get_node_or_null("BevyAppSingleton") {
if bevy_app.has_method("bulk_update_transforms_3d") {
[<post_update_godot_transforms_ $name:lower _bulk>](
change_tick,
entities,
bevy_app.upcast::<Object>(),
);
return;
}
}
}
}
[<post_update_godot_transforms_ $name:lower _individual>](change_tick, entities);
}
fn [<post_update_godot_transforms_ $name:lower _bulk>](
change_tick: bevy::ecs::system::SystemChangeTick,
mut entities: bevy::prelude::Query<
(
bevy::ecs::change_detection::Ref<bevy::prelude::Transform>,
&mut $crate::interop::GodotNodeHandle,
&$crate::plugins::transforms::TransformSyncMetadata,
bevy::ecs::query::AnyOf<(&$crate::interop::node_markers::Node2DMarker, &$crate::interop::node_markers::Node3DMarker)>,
),
(
bevy::ecs::query::Changed<bevy::prelude::Transform>,
$bevy_to_godot_query,
),
>,
mut batch_singleton: godot::prelude::Gd<godot::classes::Object>,
) {
use $crate::plugins::transforms::{IntoGodotTransform, IntoGodotTransform2D};
use bevy::ecs::change_detection::DetectChanges;
use godot::global::godot_print;
use godot::prelude::ToGodot;
let _span = tracing::info_span!("bulk_data_preparation_optimized", system = stringify!($name)).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", system = stringify!($name)).entered();
for (transform_ref, reference, metadata, (node2d, node3d)) in entities.iter_mut() {
if let Some(sync_tick) = metadata.last_sync_tick {
if !transform_ref
.last_changed()
.is_newer_than(sync_tick, change_tick.this_run())
{
continue;
}
}
let instance_id = reference.instance_id();
if node2d.is_some() {
let transform_2d = transform_ref.to_godot_transform_2d();
instance_ids_2d.push(instance_id.to_i64());
positions_2d.push(godot::prelude::Vector2::new(transform_2d.origin.x, transform_2d.origin.y));
rotations_2d.push(transform_2d.rotation());
scales_2d.push(godot::prelude::Vector2::new(transform_2d.scale().x, transform_2d.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 _convert_span = tracing::info_span!("convert_to_packed_arrays", system = stringify!($name)).entered();
let has_3d_updates = !instance_ids_3d.is_empty();
let has_2d_updates = !instance_ids_2d.is_empty();
drop(_convert_span);
drop(_span);
let total_updates = instance_ids_3d.len() + instance_ids_2d.len();
if total_updates > 0 {
static mut BATCH_LOG_COUNTER: u32 = 0;
unsafe {
BATCH_LOG_COUNTER += 1;
}
let _ffi_calls_span = tracing::info_span!("raw_array_ffi_calls", total_entities = total_updates, system = stringify!($name)).entered();
if has_3d_updates {
let _span = tracing::info_span!("raw_ffi_call_3d", entities = instance_ids_3d.len(), system = stringify!($name)).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(), system = stringify!($name)).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_ $name:lower _individual>](
change_tick: bevy::ecs::system::SystemChangeTick,
mut entities: bevy::prelude::Query<
(
bevy::ecs::change_detection::Ref<bevy::prelude::Transform>,
&mut $crate::interop::GodotNodeHandle,
&$crate::plugins::transforms::TransformSyncMetadata,
bevy::ecs::query::AnyOf<(&$crate::interop::node_markers::Node2DMarker, &$crate::interop::node_markers::Node3DMarker)>,
),
(
bevy::ecs::query::Changed<bevy::prelude::Transform>,
$bevy_to_godot_query,
),
>,
) {
use $crate::plugins::transforms::{IntoGodotTransform, IntoGodotTransform2D};
use bevy::ecs::change_detection::DetectChanges;
use godot::classes::{Node2D, Node3D};
for (transform_ref, mut reference, metadata, (node2d, node3d)) in entities.iter_mut() {
if let Some(sync_tick) = metadata.last_sync_tick {
if !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", system = stringify!($name)).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", system = stringify!($name)).entered();
let mut obj = reference.get::<Node3D>();
obj.set_transform(transform_ref.to_godot_transform());
}
}
}
$app.add_systems(bevy::app::Last, [<post_update_godot_transforms_ $name:lower>]);
}
};
(@generate_pre_system $app:expr, $name:ident, $godot_to_bevy_query:ty) => {
$crate::paste::paste! {
#[tracing::instrument]
#[$crate::prelude::main_thread_system]
pub fn [<pre_update_godot_transforms_ $name:lower>](
mut entities: bevy::prelude::Query<
(
&mut bevy::prelude::Transform,
&mut $crate::interop::GodotNodeHandle,
&mut $crate::plugins::transforms::TransformSyncMetadata,
bevy::ecs::query::AnyOf<(&$crate::interop::node_markers::Node2DMarker, &$crate::interop::node_markers::Node3DMarker)>,
),
$godot_to_bevy_query
>,
) {
use $crate::plugins::transforms::IntoBevyTransform;
use bevy::ecs::change_detection::DetectChanges;
use godot::classes::{Node2D, Node3D};
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());
}
}
}
$app.add_systems(bevy::app::PreUpdate, [<pre_update_godot_transforms_ $name:lower>]);
}
};
}
pub trait GodotTransformSyncPluginExt {
fn without_auto_sync(self) -> Self;
fn with_sync_mode(self, mode: crate::plugins::transforms::TransformSyncMode) -> Self;
}
impl GodotTransformSyncPluginExt for crate::plugins::transforms::GodotTransformSyncPlugin {
fn without_auto_sync(mut self) -> Self {
self.auto_sync = false;
self
}
fn with_sync_mode(mut self, mode: crate::plugins::transforms::TransformSyncMode) -> Self {
self.sync_mode = mode;
self
}
}
pub use add_transform_sync_systems;