1#[macro_export]
32macro_rules! add_transform_sync_systems {
33 ($app:expr, $($tokens:tt)*) => {
35 $crate::add_transform_sync_systems!(@parse_all $app, $($tokens)*);
36 };
37
38 (@parse_all $app:expr, $name:ident = bevy_to_godot: $query:ty, $($rest:tt)*) => {
40 $crate::add_transform_sync_systems!(@generate_post_system $app, $name, $query);
41 $crate::add_transform_sync_systems!(@parse_all $app, $($rest)*);
42 };
43
44 (@parse_all $app:expr, $name:ident = godot_to_bevy: $query:ty, $($rest:tt)*) => {
45 $crate::add_transform_sync_systems!(@generate_pre_system $app, $name, $query);
46 $crate::add_transform_sync_systems!(@parse_all $app, $($rest)*);
47 };
48
49 (@parse_all $app:expr, $name:ident = $query:ty, $($rest:tt)*) => {
50 $crate::add_transform_sync_systems!(@generate_systems $app, $name, $query, $query);
51 $crate::add_transform_sync_systems!(@parse_all $app, $($rest)*);
52 };
53
54 (@parse_all $app:expr, $name:ident = bevy_to_godot: $query:ty) => {
56 $crate::add_transform_sync_systems!(@generate_post_system $app, $name, $query);
57 };
58
59 (@parse_all $app:expr, $name:ident = godot_to_bevy: $query:ty) => {
60 $crate::add_transform_sync_systems!(@generate_pre_system $app, $name, $query);
61 };
62
63 (@parse_all $app:expr, $name:ident = $query:ty) => {
64 $crate::add_transform_sync_systems!(@generate_systems $app, $name, $query, $query);
65 };
66
67 (@parse_all $app:expr,) => {};
69 (@parse_all $app:expr) => {};
70
71 (@generate_systems $app:expr, $name:ident, $bevy_to_godot_query:ty, $godot_to_bevy_query:ty) => {
72 $crate::add_transform_sync_systems!(@generate_post_system $app, $name, $bevy_to_godot_query);
73 $crate::add_transform_sync_systems!(@generate_pre_system $app, $name, $godot_to_bevy_query);
74 };
75
76 (@generate_post_system $app:expr, $name:ident, $bevy_to_godot_query:ty) => {
77 $crate::paste::paste! {
78 #[cfg(debug_assertions)]
79 #[tracing::instrument]
80 pub fn [<post_update_godot_transforms_ $name:lower>](
81 change_tick: $crate::bevy_ecs::system::SystemChangeTick,
82 entities: $crate::bevy_ecs::system::Query<
83 (
84 $crate::bevy_ecs::change_detection::Ref<$crate::bevy_transform::components::Transform>,
85 &$crate::interop::GodotNodeHandle,
86 &$crate::plugins::transforms::TransformSyncMetadata,
87 $crate::bevy_ecs::query::AnyOf<(&$crate::interop::node_markers::Node2DMarker, &$crate::interop::node_markers::Node3DMarker)>,
88 ),
89 (
90 $crate::bevy_ecs::query::Changed<$crate::bevy_transform::components::Transform>,
91 $bevy_to_godot_query,
92 ),
93 >,
94 mut godot: $crate::interop::GodotAccess,
95 bulk_ops_cache: $crate::bevy_ecs::system::NonSendMut<$crate::interop::BulkOperationsCache>,
96 ) {
97 if let Some(bulk_ops_node) = bulk_ops_cache.get() {
98 [<post_update_godot_transforms_ $name:lower _bulk>](
99 change_tick,
100 entities,
101 bulk_ops_node,
102 );
103 return;
104 }
105 [<post_update_godot_transforms_ $name:lower _individual>](change_tick, entities, &mut godot);
106 }
107
108 #[cfg(not(debug_assertions))]
109 #[tracing::instrument]
110 pub fn [<post_update_godot_transforms_ $name:lower>](
111 change_tick: $crate::bevy_ecs::system::SystemChangeTick,
112 entities: $crate::bevy_ecs::system::Query<
113 (
114 $crate::bevy_ecs::change_detection::Ref<$crate::bevy_transform::components::Transform>,
115 &$crate::interop::GodotNodeHandle,
116 &$crate::plugins::transforms::TransformSyncMetadata,
117 $crate::bevy_ecs::query::AnyOf<(&$crate::interop::node_markers::Node2DMarker, &$crate::interop::node_markers::Node3DMarker)>,
118 ),
119 (
120 $crate::bevy_ecs::query::Changed<$crate::bevy_transform::components::Transform>,
121 $bevy_to_godot_query,
122 ),
123 >,
124 mut godot: $crate::interop::GodotAccess,
125 ) {
126 [<post_update_godot_transforms_ $name:lower _individual>](change_tick, entities, &mut godot);
127 }
128
129 #[cfg(debug_assertions)]
130 fn [<post_update_godot_transforms_ $name:lower _bulk>](
131 change_tick: $crate::bevy_ecs::system::SystemChangeTick,
132 mut entities: $crate::bevy_ecs::system::Query<
133 (
134 $crate::bevy_ecs::change_detection::Ref<$crate::bevy_transform::components::Transform>,
135 &$crate::interop::GodotNodeHandle,
136 &$crate::plugins::transforms::TransformSyncMetadata,
137 $crate::bevy_ecs::query::AnyOf<(&$crate::interop::node_markers::Node2DMarker, &$crate::interop::node_markers::Node3DMarker)>,
138 ),
139 (
140 $crate::bevy_ecs::query::Changed<$crate::bevy_transform::components::Transform>,
141 $bevy_to_godot_query,
142 ),
143 >,
144 mut batch_singleton: godot::prelude::Gd<godot::classes::Object>,
145 ) {
146 use $crate::plugins::transforms::{IntoGodotTransform, IntoGodotTransform2D};
147 use $crate::bevy_ecs::change_detection::DetectChanges;
148 use godot::global::godot_print;
149 use godot::prelude::ToGodot;
150
151 let _span = tracing::info_span!("bulk_data_preparation_optimized", system = stringify!($name)).entered();
152
153 let entity_count = entities.iter().count();
155 let mut instance_ids_3d = Vec::with_capacity(entity_count);
156 let mut positions_3d = Vec::with_capacity(entity_count);
157 let mut rotations_3d = Vec::with_capacity(entity_count);
158 let mut scales_3d = Vec::with_capacity(entity_count);
159
160 let mut instance_ids_2d = Vec::with_capacity(entity_count);
161 let mut positions_2d = Vec::with_capacity(entity_count);
162 let mut rotations_2d = Vec::with_capacity(entity_count);
163 let mut scales_2d = Vec::with_capacity(entity_count);
164
165 let _collect_span = tracing::info_span!("collect_raw_arrays", system = stringify!($name)).entered();
167 for (transform_ref, reference, metadata, (node2d, node3d)) in entities.iter_mut() {
168 if let Some(sync_tick) = metadata.last_sync_tick {
170 if !transform_ref
171 .last_changed()
172 .is_newer_than(sync_tick, change_tick.this_run())
173 {
174 continue;
176 }
177 }
178
179 let instance_id = reference.instance_id();
180
181 if node2d.is_some() {
182 let transform_2d = transform_ref.to_godot_transform_2d();
183 instance_ids_2d.push(instance_id.to_i64());
184 positions_2d.push(godot::prelude::Vector2::new(transform_2d.origin.x, transform_2d.origin.y));
185 rotations_2d.push(transform_2d.rotation());
186 scales_2d.push(godot::prelude::Vector2::new(transform_2d.scale().x, transform_2d.scale().y));
187 } else if node3d.is_some() {
188 instance_ids_3d.push(instance_id.to_i64());
190 positions_3d.push(godot::prelude::Vector3::new(
191 transform_ref.translation.x,
192 transform_ref.translation.y,
193 transform_ref.translation.z
194 ));
195
196 rotations_3d.push(godot::prelude::Vector4 {
197 x: transform_ref.rotation.x,
198 y: transform_ref.rotation.y,
199 z: transform_ref.rotation.z,
200 w: transform_ref.rotation.w,
201 });
202
203 scales_3d.push(godot::prelude::Vector3::new(
204 transform_ref.scale.x,
205 transform_ref.scale.y,
206 transform_ref.scale.z
207 ));
208 }
209 }
210 drop(_collect_span);
211
212 let _convert_span = tracing::info_span!("convert_to_packed_arrays", system = stringify!($name)).entered();
214 let has_3d_updates = !instance_ids_3d.is_empty();
215 let has_2d_updates = !instance_ids_2d.is_empty();
216 drop(_convert_span);
217
218 drop(_span);
220
221 let total_updates = instance_ids_3d.len() + instance_ids_2d.len();
223 if total_updates > 0 {
224 static mut BATCH_LOG_COUNTER: u32 = 0;
225 unsafe {
226 BATCH_LOG_COUNTER += 1;
227 }
228
229 let _ffi_calls_span = tracing::info_span!("raw_array_ffi_calls", total_entities = total_updates, system = stringify!($name)).entered();
230
231 if has_3d_updates {
232 let _span = tracing::info_span!("raw_ffi_call_3d", entities = instance_ids_3d.len(), system = stringify!($name)).entered();
233
234 let instance_ids_packed = godot::prelude::PackedInt64Array::from(instance_ids_3d.as_slice());
236 let positions_packed = godot::prelude::PackedVector3Array::from(positions_3d.as_slice());
237 let rotations_packed = godot::prelude::PackedVector4Array::from(rotations_3d.as_slice());
238 let scales_packed = godot::prelude::PackedVector3Array::from(scales_3d.as_slice());
239
240 batch_singleton.call("bulk_update_transforms_3d", &[
241 instance_ids_packed.to_variant(),
242 positions_packed.to_variant(),
243 rotations_packed.to_variant(),
244 scales_packed.to_variant()
245 ]);
246 }
247 if has_2d_updates {
248 let _span = tracing::info_span!("raw_ffi_call_2d", entities = instance_ids_2d.len(), system = stringify!($name)).entered();
249
250 let instance_ids_packed = godot::prelude::PackedInt64Array::from(instance_ids_2d.as_slice());
252 let positions_packed = godot::prelude::PackedVector2Array::from(positions_2d.as_slice());
253 let rotations_packed = godot::prelude::PackedFloat32Array::from(rotations_2d.as_slice());
254 let scales_packed = godot::prelude::PackedVector2Array::from(scales_2d.as_slice());
255
256 batch_singleton.call("bulk_update_transforms_2d", &[
257 instance_ids_packed.to_variant(),
258 positions_packed.to_variant(),
259 rotations_packed.to_variant(),
260 scales_packed.to_variant()
261 ]);
262 }
263 }
264 }
265
266 fn [<post_update_godot_transforms_ $name:lower _individual>](
267 change_tick: $crate::bevy_ecs::system::SystemChangeTick,
268 mut entities: $crate::bevy_ecs::system::Query<
269 (
270 $crate::bevy_ecs::change_detection::Ref<$crate::bevy_transform::components::Transform>,
271 &$crate::interop::GodotNodeHandle,
272 &$crate::plugins::transforms::TransformSyncMetadata,
273 $crate::bevy_ecs::query::AnyOf<(&$crate::interop::node_markers::Node2DMarker, &$crate::interop::node_markers::Node3DMarker)>,
274 ),
275 (
276 $crate::bevy_ecs::query::Changed<$crate::bevy_transform::components::Transform>,
277 $bevy_to_godot_query,
278 ),
279 >,
280 godot: &mut $crate::interop::GodotAccess,
281 ) {
282 use $crate::plugins::transforms::{IntoGodotTransform, IntoGodotTransform2D};
283 use $crate::bevy_ecs::change_detection::DetectChanges;
284 use godot::classes::{Node2D, Node3D};
285
286 for (transform_ref, reference, metadata, (node2d, node3d)) in entities.iter_mut() {
288 if let Some(sync_tick) = metadata.last_sync_tick {
290 if !transform_ref
291 .last_changed()
292 .is_newer_than(sync_tick, change_tick.this_run())
293 {
294 continue;
296 }
297 }
298
299 if node2d.is_some() {
301 let _span = tracing::info_span!("individual_ffi_call_2d", system = stringify!($name)).entered();
302 let mut obj = godot.get::<Node2D>(*reference);
303 obj.set_transform(transform_ref.to_godot_transform_2d());
304 } else if node3d.is_some() {
305 let _span = tracing::info_span!("individual_ffi_call_3d", system = stringify!($name)).entered();
306 let mut obj = godot.get::<Node3D>(*reference);
307 obj.set_transform(transform_ref.to_godot_transform());
308 }
309 }
310 }
311
312 $app.add_systems($crate::bevy_app::Last, [<post_update_godot_transforms_ $name:lower>]);
313 }
314 };
315
316 (@generate_pre_system $app:expr, $name:ident, $godot_to_bevy_query:ty) => {
317 $crate::paste::paste! {
318 #[cfg(debug_assertions)]
319 #[tracing::instrument]
320 pub fn [<pre_update_godot_transforms_ $name:lower>](
321 entities: $crate::bevy_ecs::system::Query<
322 (
323 $crate::bevy_ecs::entity::Entity,
324 &mut $crate::bevy_transform::components::Transform,
325 &$crate::interop::GodotNodeHandle,
326 &mut $crate::plugins::transforms::TransformSyncMetadata,
327 $crate::bevy_ecs::query::AnyOf<(&$crate::interop::node_markers::Node2DMarker, &$crate::interop::node_markers::Node3DMarker)>,
328 ),
329 $godot_to_bevy_query
330 >,
331 mut godot: $crate::interop::GodotAccess,
332 bulk_ops_cache: $crate::bevy_ecs::system::NonSendMut<$crate::interop::BulkOperationsCache>,
333 ) {
334 if let Some(bulk_ops_node) = bulk_ops_cache.get() {
335 [<pre_update_godot_transforms_ $name:lower _bulk>](
336 entities,
337 bulk_ops_node,
338 );
339 return;
340 }
341 [<pre_update_godot_transforms_ $name:lower _individual>](entities, &mut godot);
342 }
343
344 #[cfg(not(debug_assertions))]
345 #[tracing::instrument]
346 pub fn [<pre_update_godot_transforms_ $name:lower>](
347 entities: $crate::bevy_ecs::system::Query<
348 (
349 $crate::bevy_ecs::entity::Entity,
350 &mut $crate::bevy_transform::components::Transform,
351 &$crate::interop::GodotNodeHandle,
352 &mut $crate::plugins::transforms::TransformSyncMetadata,
353 $crate::bevy_ecs::query::AnyOf<(&$crate::interop::node_markers::Node2DMarker, &$crate::interop::node_markers::Node3DMarker)>,
354 ),
355 $godot_to_bevy_query
356 >,
357 mut godot: $crate::interop::GodotAccess,
358 ) {
359 [<pre_update_godot_transforms_ $name:lower _individual>](entities, &mut godot);
360 }
361
362 #[cfg(debug_assertions)]
363 fn [<pre_update_godot_transforms_ $name:lower _bulk>](
364 mut entities: $crate::bevy_ecs::system::Query<
365 (
366 $crate::bevy_ecs::entity::Entity,
367 &mut $crate::bevy_transform::components::Transform,
368 &$crate::interop::GodotNodeHandle,
369 &mut $crate::plugins::transforms::TransformSyncMetadata,
370 $crate::bevy_ecs::query::AnyOf<(&$crate::interop::node_markers::Node2DMarker, &$crate::interop::node_markers::Node3DMarker)>,
371 ),
372 $godot_to_bevy_query
373 >,
374 mut batch_singleton: godot::prelude::Gd<godot::classes::Object>,
375 ) {
376 use $crate::bevy_ecs::change_detection::DetectChanges;
377 use godot::prelude::ToGodot;
378
379 let _span = tracing::info_span!("bulk_read_preparation", system = stringify!($name)).entered();
380
381 let mut entities_3d: Vec<($crate::bevy_ecs::entity::Entity, i64)> = Vec::new();
383 let mut entities_2d: Vec<($crate::bevy_ecs::entity::Entity, i64)> = Vec::new();
384
385 for (entity, _, reference, _, (node2d, node3d)) in entities.iter() {
386 let instance_id = reference.instance_id().to_i64();
387 if node2d.is_some() {
388 entities_2d.push((entity, instance_id));
389 } else if node3d.is_some() {
390 entities_3d.push((entity, instance_id));
391 }
392 }
393
394 drop(_span);
395
396 if !entities_3d.is_empty() {
398 let _span = tracing::info_span!("bulk_read_3d", count = entities_3d.len(), system = stringify!($name)).entered();
399
400 let instance_ids: Vec<i64> = entities_3d.iter().map(|(_, id)| *id).collect();
401 let ids_packed = godot::prelude::PackedInt64Array::from(instance_ids.as_slice());
402
403 let result = batch_singleton
404 .call("bulk_get_transforms_3d", &[ids_packed.to_variant()])
405 .to::<godot::builtin::VarDictionary>();
406
407 if let (Some(positions), Some(rotations), Some(scales)) = (
408 result
409 .get("positions")
410 .map(|v| v.to::<godot::builtin::PackedVector3Array>()),
411 result
412 .get("rotations")
413 .map(|v| v.to::<godot::builtin::PackedVector4Array>()),
414 result
415 .get("scales")
416 .map(|v| v.to::<godot::builtin::PackedVector3Array>()),
417 ) {
418 for (i, (entity, _)) in entities_3d.iter().enumerate() {
419 if let Ok((_, mut bevy_transform, _, mut metadata, _)) = entities.get_mut(*entity) {
420 if let (Some(pos), Some(rot), Some(scale)) =
421 (positions.get(i), rotations.get(i), scales.get(i))
422 {
423 let new_bevy_transform = $crate::bevy_transform::components::Transform {
424 translation: $crate::bevy_math::Vec3::new(pos.x, pos.y, pos.z),
425 rotation: $crate::bevy_math::Quat::from_xyzw(rot.x, rot.y, rot.z, rot.w),
426 scale: $crate::bevy_math::Vec3::new(scale.x, scale.y, scale.z),
427 };
428
429 if *bevy_transform != new_bevy_transform {
430 *bevy_transform = new_bevy_transform;
431 metadata.last_sync_tick = Some(bevy_transform.last_changed());
432 }
433 }
434 }
435 }
436 }
437 }
438
439 if !entities_2d.is_empty() {
441 let _span = tracing::info_span!("bulk_read_2d", count = entities_2d.len(), system = stringify!($name)).entered();
442
443 let instance_ids: Vec<i64> = entities_2d.iter().map(|(_, id)| *id).collect();
444 let ids_packed = godot::prelude::PackedInt64Array::from(instance_ids.as_slice());
445
446 let result = batch_singleton
447 .call("bulk_get_transforms_2d", &[ids_packed.to_variant()])
448 .to::<godot::builtin::VarDictionary>();
449
450 if let (Some(positions), Some(rotations), Some(scales)) = (
451 result
452 .get("positions")
453 .map(|v| v.to::<godot::builtin::PackedVector2Array>()),
454 result
455 .get("rotations")
456 .map(|v| v.to::<godot::builtin::PackedFloat32Array>()),
457 result
458 .get("scales")
459 .map(|v| v.to::<godot::builtin::PackedVector2Array>()),
460 ) {
461 for (i, (entity, _)) in entities_2d.iter().enumerate() {
462 if let Ok((_, mut bevy_transform, _, mut metadata, _)) = entities.get_mut(*entity) {
463 if let (Some(pos), Some(rot), Some(scale)) =
464 (positions.get(i), rotations.get(i), scales.get(i))
465 {
466 let new_bevy_transform = $crate::bevy_transform::components::Transform {
467 translation: $crate::bevy_math::Vec3::new(pos.x, pos.y, 0.0),
468 rotation: $crate::bevy_math::Quat::from_rotation_z(rot),
469 scale: $crate::bevy_math::Vec3::new(scale.x, scale.y, 1.0),
470 };
471
472 if *bevy_transform != new_bevy_transform {
473 *bevy_transform = new_bevy_transform;
474 metadata.last_sync_tick = Some(bevy_transform.last_changed());
475 }
476 }
477 }
478 }
479 }
480 }
481 }
482
483 fn [<pre_update_godot_transforms_ $name:lower _individual>](
484 mut entities: $crate::bevy_ecs::system::Query<
485 (
486 $crate::bevy_ecs::entity::Entity,
487 &mut $crate::bevy_transform::components::Transform,
488 &$crate::interop::GodotNodeHandle,
489 &mut $crate::plugins::transforms::TransformSyncMetadata,
490 $crate::bevy_ecs::query::AnyOf<(&$crate::interop::node_markers::Node2DMarker, &$crate::interop::node_markers::Node3DMarker)>,
491 ),
492 $godot_to_bevy_query
493 >,
494 godot: &mut $crate::interop::GodotAccess,
495 ) {
496 use $crate::plugins::transforms::IntoBevyTransform;
497 use $crate::bevy_ecs::change_detection::DetectChanges;
498 use godot::classes::{Node2D, Node3D};
499
500 for (_, mut bevy_transform, reference, mut metadata, (node2d, node3d)) in entities.iter_mut() {
501 let new_bevy_transform = if node2d.is_some() {
502 godot
503 .get::<Node2D>(*reference)
504 .get_transform()
505 .to_bevy_transform()
506 } else if node3d.is_some() {
507 godot
508 .get::<Node3D>(*reference)
509 .get_transform()
510 .to_bevy_transform()
511 } else {
512 panic!("Expected AnyOf to match either a Node2D or a Node3D, is there a bug in bevy?");
513 };
514
515 if *bevy_transform != new_bevy_transform {
517 *bevy_transform = new_bevy_transform;
518
519 metadata.last_sync_tick = Some(bevy_transform.last_changed());
524 }
525 }
526 }
527
528 $app.add_systems($crate::bevy_app::PreUpdate, [<pre_update_godot_transforms_ $name:lower>]);
529 }
530 };
531
532}
533
534pub trait GodotTransformSyncPluginExt {
536 fn without_auto_sync(self) -> Self;
538
539 fn with_sync_mode(self, mode: crate::plugins::transforms::TransformSyncMode) -> Self;
541}
542
543impl GodotTransformSyncPluginExt for crate::plugins::transforms::GodotTransformSyncPlugin {
544 fn without_auto_sync(mut self) -> Self {
545 self.auto_sync = false;
546 self
547 }
548
549 fn with_sync_mode(mut self, mode: crate::plugins::transforms::TransformSyncMode) -> Self {
550 self.sync_mode = mode;
551 self
552 }
553}
554
555pub use add_transform_sync_systems;