1use crate::interop::{GodotAccess, GodotNodeHandle};
2use bevy_app::{App, First, Last, Plugin};
3use bevy_ecs::{
4 component::Component,
5 entity::Entity,
6 event::Event,
7 prelude::Resource,
8 system::{Commands, Query, Res, SystemParam},
9};
10use crossbeam_channel::Sender;
11use godot::{
12 classes::Node,
13 obj::Gd,
14 prelude::{Callable, Variant},
15};
16use parking_lot::Mutex;
17use std::fmt::Debug;
18use std::sync::Arc;
19use tracing::error;
20
21pub(crate) trait SignalDispatch: Send {
23 fn trigger_in_world(self: Box<Self>, world: &mut bevy_ecs::world::World);
24}
25
26struct SignalEnvelope<T: Event + Clone + Send + 'static> {
28 event: T,
29}
30
31impl<T: Event + Clone + Send + 'static> SignalDispatch for SignalEnvelope<T>
32where
33 for<'a> T::Trigger<'a>: Default,
34{
35 fn trigger_in_world(self: Box<Self>, world: &mut bevy_ecs::world::World) {
36 world.trigger(self.event);
37 }
38}
39
40#[derive(Resource)]
43pub(crate) struct SignalReceiver(pub Mutex<crossbeam_channel::Receiver<Box<dyn SignalDispatch>>>);
44
45impl SignalReceiver {
46 pub fn new(receiver: crossbeam_channel::Receiver<Box<dyn SignalDispatch>>) -> Self {
47 Self(Mutex::new(receiver))
48 }
49}
50
51#[doc(hidden)]
52#[derive(Resource)]
53pub(crate) struct SignalSender(pub crossbeam_channel::Sender<Box<dyn SignalDispatch>>);
54
55#[derive(Resource, Default)]
56struct PendingSignalConnections {
57 queue: Mutex<Vec<Box<dyn PendingSignalConnection>>>,
58}
59
60trait PendingSignalConnection: Send {
61 fn connect(self: Box<Self>, godot: &mut GodotAccess);
62}
63
64impl PendingSignalConnections {
65 fn push(&self, connection: Box<dyn PendingSignalConnection>) {
66 self.queue.lock().push(connection);
67 }
68
69 fn drain(&self) -> Vec<Box<dyn PendingSignalConnection>> {
70 self.queue.lock().drain(..).collect()
71 }
72}
73
74fn ensure_signal_connection_queue(app: &mut App) {
75 if !app.world().contains_resource::<PendingSignalConnections>() {
76 app.init_resource::<PendingSignalConnections>()
77 .add_systems(Last, process_pending_signal_connections);
80 }
81}
82
83fn process_pending_signal_connections(
84 pending: Res<PendingSignalConnections>,
85 mut godot: GodotAccess,
86) {
87 for connection in pending.drain() {
88 connection.connect(&mut godot);
89 }
90}
91
92fn connect_signal<T>(
93 godot: &mut GodotAccess,
94 node: GodotNodeHandle,
95 signal_name: &str,
96 source_entity: Option<Entity>,
97 mapper: Box<
98 dyn FnMut(&[Variant], GodotNodeHandle, Option<Entity>) -> Option<T> + Send + 'static,
99 >,
100 sender: Sender<Box<dyn SignalDispatch>>,
101) where
102 T: Event + Clone + Send + 'static,
103 for<'a> T::Trigger<'a>: Default,
104{
105 let mut node_ref = godot.get::<Node>(node);
106 let signal_name_copy = signal_name.to_string();
107 let source_node_handle = node;
108 let mut mapper = mapper;
109
110 let closure = move |args: &[&Variant]| -> Variant {
111 let owned: Vec<Variant> = args.iter().map(|&v| v.clone()).collect();
113 let event = mapper(&owned, source_node_handle, source_entity);
114 if let Some(event) = event {
115 let _ = sender.send(Box::new(SignalEnvelope { event }));
116 }
117 Variant::nil()
118 };
119
120 let callable = Callable::from_fn(&format!("signal_handler_{signal_name_copy}"), closure);
121 node_ref.connect(signal_name, &callable);
122}
123
124pub struct GodotSignalsPlugin<T>
158where
159 T: Event + Clone + Send + 'static,
160 for<'a> T::Trigger<'a>: Default,
161{
162 _phantom: std::marker::PhantomData<T>,
163}
164
165impl<T> Default for GodotSignalsPlugin<T>
166where
167 T: Event + Clone + Send + 'static,
168 for<'a> T::Trigger<'a>: Default,
169{
170 fn default() -> Self {
171 Self {
172 _phantom: Default::default(),
173 }
174 }
175}
176
177impl<T> Plugin for GodotSignalsPlugin<T>
178where
179 T: Event + Clone + Send + 'static,
180 for<'a> T::Trigger<'a>: Default,
181{
182 fn build(&self, app: &mut App) {
183 ensure_signal_connection_queue(app);
184
185 if !app.world().contains_resource::<SignalSender>() {
187 let (sender, receiver) = crossbeam_channel::unbounded::<Box<dyn SignalDispatch>>();
188 app.world_mut().insert_resource(SignalSender(sender));
189 app.world_mut()
190 .insert_resource(SignalReceiver::new(receiver));
191
192 app.add_systems(First, drain_and_trigger_signals);
194 }
195
196 app.add_systems(First, process_deferred_signal_connections::<T>);
198 }
199}
200
201fn drain_and_trigger_signals(world: &mut bevy_ecs::world::World) {
203 let mut pending: Vec<Box<dyn SignalDispatch>> = Vec::new();
205 if let Some(receiver) = world.get_resource::<SignalReceiver>() {
206 let guard = receiver.0.lock();
207 pending.extend(guard.try_iter());
208 }
209 for dispatch in pending.drain(..) {
210 dispatch.trigger_in_world(world);
211 }
212}
213
214#[derive(SystemParam)]
236pub struct GodotSignals<'w, T>
237where
238 T: Event + Clone + Send + 'static,
239 for<'a> T::Trigger<'a>: Default,
240{
241 sender: Res<'w, SignalSender>,
242 pending: Res<'w, PendingSignalConnections>,
243 _marker: std::marker::PhantomData<T>,
244}
245
246impl<'w, T> GodotSignals<'w, T>
247where
248 T: Event + Clone + Send + 'static,
249 for<'a> T::Trigger<'a>: Default,
250{
251 pub fn connect<F>(
263 &self,
264 node: GodotNodeHandle,
265 signal_name: &str,
266 source_entity: Option<Entity>,
267 mapper: F,
268 ) where
269 F: FnMut(&[Variant], GodotNodeHandle, Option<Entity>) -> Option<T> + Send + 'static,
270 {
271 self.pending.push(Box::new(PendingSignalConnectionImpl {
272 node,
273 signal_name: signal_name.to_string(),
274 source_entity,
275 mapper: Box::new(mapper),
276 sender: self.sender.0.clone(),
277 _marker: std::marker::PhantomData,
278 }));
279 }
280
281 pub fn connect_object<O, F>(&self, object: Gd<O>, signal_name: &str, mapper: F)
316 where
317 O: godot::obj::Inherits<godot::classes::Object> + godot::obj::GodotClass,
318 F: FnMut(&[Variant]) -> Option<T> + Send + 'static,
319 {
320 self.pending.push(Box::new(PendingDirectNodeConnection {
321 instance_id: object.instance_id(),
322 signal_name: signal_name.to_string(),
323 mapper: Box::new(mapper),
324 sender: self.sender.0.clone(),
325 _marker: std::marker::PhantomData,
326 }));
327 }
328}
329
330#[deprecated(note = "Use GodotSignals instead")]
332pub type TypedGodotSignals<'w, T> = GodotSignals<'w, T>;
333
334struct PendingSignalConnectionImpl<T>
335where
336 T: Event + Clone + Send + 'static,
337 for<'a> T::Trigger<'a>: Default,
338{
339 node: GodotNodeHandle,
340 signal_name: String,
341 source_entity: Option<Entity>,
342 mapper:
343 Box<dyn FnMut(&[Variant], GodotNodeHandle, Option<Entity>) -> Option<T> + Send + 'static>,
344 sender: Sender<Box<dyn SignalDispatch>>,
345 _marker: std::marker::PhantomData<T>,
346}
347
348impl<T> PendingSignalConnection for PendingSignalConnectionImpl<T>
349where
350 T: Event + Clone + Send + 'static,
351 for<'a> T::Trigger<'a>: Default,
352{
353 fn connect(self: Box<Self>, godot: &mut GodotAccess) {
354 let PendingSignalConnectionImpl {
355 node,
356 signal_name,
357 source_entity,
358 mapper,
359 sender,
360 _marker: _,
361 } = *self;
362 connect_signal(godot, node, &signal_name, source_entity, mapper, sender);
363 }
364}
365
366struct PendingDirectNodeConnection<T>
368where
369 T: Event + Clone + Send + 'static,
370 for<'a> T::Trigger<'a>: Default,
371{
372 instance_id: godot::obj::InstanceId,
373 signal_name: String,
374 mapper: Box<dyn FnMut(&[Variant]) -> Option<T> + Send + 'static>,
375 sender: Sender<Box<dyn SignalDispatch>>,
376 _marker: std::marker::PhantomData<T>,
377}
378
379impl<T> PendingSignalConnection for PendingDirectNodeConnection<T>
380where
381 T: Event + Clone + Send + 'static,
382 for<'a> T::Trigger<'a>: Default,
383{
384 fn connect(self: Box<Self>, _godot: &mut GodotAccess) {
385 let PendingDirectNodeConnection {
388 instance_id,
389 signal_name,
390 mut mapper,
391 sender,
392 _marker: _,
393 } = *self;
394
395 let Ok(mut node) = Gd::<godot::classes::Object>::try_from_instance_id(instance_id) else {
396 error!(
397 "Failed to get object with instance_id {:?} for signal connection",
398 instance_id
399 );
400 return;
401 };
402
403 let signal_name_copy = signal_name.clone();
404
405 let closure = move |args: &[&Variant]| -> Variant {
406 let owned: Vec<Variant> = args.iter().map(|&v| v.clone()).collect();
407 if let Some(event) = mapper(&owned) {
408 let _ = sender.send(Box::new(SignalEnvelope { event }));
409 }
410 Variant::nil()
411 };
412
413 let callable = Callable::from_fn(&format!("signal_handler_{signal_name_copy}"), closure);
414 node.connect(&signal_name, &callable);
415 }
416}
417
418fn process_deferred_signal_connections<T>(
420 mut commands: Commands,
421 mut query: Query<(Entity, &GodotNodeHandle, &mut DeferredSignalConnections<T>)>,
422 signals: GodotSignals<T>,
423) where
424 T: Event + Clone + Send + 'static,
425 for<'a> T::Trigger<'a>: Default,
426{
427 for (entity, handle, mut deferred) in query.iter_mut() {
428 for conn in deferred.connections.drain(..) {
429 let signal = conn.signal_name;
430 let mapper = conn.mapper;
431 signals.connect(
432 *handle,
433 &signal,
434 Some(entity),
435 move |args, node_handle, ent| (mapper)(args, node_handle, ent),
436 );
437 }
438 commands
440 .entity(entity)
441 .remove::<DeferredSignalConnections<T>>();
442 }
443}
444
445pub struct DeferredConnection<T: Event + Clone + Send + 'static> {
451 pub signal_name: String,
453 pub mapper: Arc<
455 dyn Fn(&[Variant], GodotNodeHandle, Option<Entity>) -> Option<T> + Send + Sync + 'static,
456 >,
457}
458
459impl<T: Event + Clone + Send + 'static> Debug for DeferredConnection<T> {
460 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
461 write!(
462 f,
463 "DeferredConnection {{ signal_name: {:?} }}",
464 self.signal_name
465 )
466 }
467}
468
469#[derive(Component, Debug)]
475pub struct DeferredSignalConnections<T: Event + Clone + Send + 'static> {
476 pub connections: Vec<DeferredConnection<T>>,
478}
479
480impl<T: Event + Clone + Send + 'static> Default for DeferredSignalConnections<T> {
481 fn default() -> Self {
482 Self::new()
483 }
484}
485
486impl<T: Event + Clone + Send + 'static> DeferredSignalConnections<T> {
487 pub fn new() -> Self {
489 Self {
490 connections: Vec::new(),
491 }
492 }
493
494 pub fn with_connection<F>(signal_name: impl Into<String>, mapper: F) -> Self
496 where
497 F: Fn(&[Variant], GodotNodeHandle, Option<Entity>) -> Option<T> + Send + Sync + 'static,
498 {
499 Self {
500 connections: vec![DeferredConnection {
501 signal_name: signal_name.into(),
502 mapper: Arc::new(mapper),
503 }],
504 }
505 }
506
507 pub fn push<F>(&mut self, signal_name: impl Into<String>, mapper: F)
509 where
510 F: Fn(&[Variant], GodotNodeHandle, Option<Entity>) -> Option<T> + Send + Sync + 'static,
511 {
512 self.connections.push(DeferredConnection {
513 signal_name: signal_name.into(),
514 mapper: Arc::new(mapper),
515 });
516 }
517}
518
519#[deprecated(note = "Use DeferredSignalConnections instead")]
521pub type TypedDeferredSignalConnections<T> = DeferredSignalConnections<T>;
522
523#[deprecated(note = "Use DeferredConnection instead")]
525pub type TypedDeferredConnection<T> = DeferredConnection<T>;
526
527#[doc(hidden)]
529pub(crate) trait DeferredSignalConnectionTrait: Send + Sync + Debug {
530 fn connect(&self, root_node: &Gd<Node>, entity: Entity, sender: &SignalSender);
531}
532
533#[doc(hidden)]
535#[derive(Debug)]
536pub(crate) struct SignalConnectionSpec<T>
537where
538 T: Event + Clone + Send + 'static,
539 for<'a> T::Trigger<'a>: Default,
540{
541 pub(crate) node_path: String,
542 pub(crate) signal_name: String,
543 pub(crate) connections: DeferredSignalConnections<T>,
544}
545
546#[doc(hidden)]
547impl<T> DeferredSignalConnectionTrait for SignalConnectionSpec<T>
548where
549 T: Event + Clone + Send + Debug + 'static,
550 for<'a> T::Trigger<'a>: Default,
551{
552 fn connect(&self, root_node: &Gd<Node>, source_entity: Entity, sender: &SignalSender) {
553 let Some(mut target_node) = root_node.get_node_or_null(self.node_path.as_str()) else {
554 error!(
555 "Failed to find node at path '{}' for signal connection",
556 self.node_path
557 );
558 return;
559 };
560
561 for connection in self.connections.connections.iter() {
562 let source_node_id = GodotNodeHandle::from(target_node.instance_id());
563 let sender_copy = sender.0.clone();
564 let mapper = connection.mapper.clone();
565 let signal_name = self.signal_name.clone();
566
567 let closure = move |args: &[&Variant]| -> Variant {
568 let owned: Vec<Variant> = args.iter().map(|&v| v.clone()).collect();
569 if let Some(event) = mapper(&owned, source_node_id, Some(source_entity)) {
570 let _ = sender_copy.send(Box::new(SignalEnvelope { event }));
571 }
572 Variant::nil()
573 };
574
575 target_node.connect(
576 &signal_name,
577 &Callable::from_fn(&format!("signal_handler_{signal_name}"), closure),
578 );
579 }
580 }
581}