Skip to main content

godot_bevy/plugins/
core.rs

1#![allow(deprecated)] // TODO: remove this once we've removed SystemDeltaTimer
2
3use bevy_app::{App, Plugin};
4use bevy_ecs::component::Component;
5use bevy_ecs::event::EntityEvent;
6use bevy_ecs::lifecycle::Remove;
7use bevy_ecs::observer::On;
8use bevy_ecs::prelude::{Name, Resource};
9use bevy_ecs::schedule::{Schedule, ScheduleLabel};
10use bevy_ecs::system::{Local, Query, SystemParam};
11use std::any::TypeId;
12use std::marker::PhantomData;
13use std::time::{Duration, Instant};
14
15/// Schedule that runs during Godot's physics_process at physics frame rate.
16/// This schedule runs just before the PhysicsUpdate schedule.
17#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
18pub struct PrePhysicsUpdate;
19
20/// Schedule that runs during Godot's physics_process at physics frame rate.
21/// Use this for movement, physics, and systems that need to sync with Godot's physics timing.
22#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
23pub struct PhysicsUpdate;
24
25/// Resource containing Godot's physics delta time for the current frame
26#[derive(Resource, Default)]
27pub struct PhysicsDelta {
28    pub delta_seconds: f32,
29}
30
31impl PhysicsDelta {
32    pub fn new(delta: f64) -> Self {
33        Self {
34            delta_seconds: delta as f32,
35        }
36    }
37
38    pub fn delta(&self) -> Duration {
39        Duration::from_secs_f32(self.delta_seconds)
40    }
41}
42
43use crate::interop::{GodotAccess, GodotMainThread, GodotNode, GodotNodeHandle};
44use bevy_ecs::system::EntityCommands;
45use godot::classes::Node;
46use tracing::debug;
47
48/// Function that adds a component to an entity with access to the Godot node
49type ComponentInserter = Box<dyn Fn(&mut EntityCommands, &mut GodotNode) + Send + Sync>;
50
51/// Registry for components that should be added to entities spawned from the scene tree
52#[derive(Resource, Default)]
53pub struct SceneTreeComponentRegistry {
54    /// Components to add to every entity spawned from scene tree
55    /// Stored as (TypeId, inserter) to avoid duplicates
56    components: Vec<(TypeId, ComponentInserter)>,
57}
58
59impl SceneTreeComponentRegistry {
60    /// Register a component type to be added to all scene tree entities
61    pub fn register<C>(&mut self)
62    where
63        C: Component + Default,
64    {
65        let type_id = TypeId::of::<C>();
66
67        // Check if already registered
68        if self.components.iter().any(|(id, _)| *id == type_id) {
69            return;
70        }
71
72        let inserter = Box::new(|entity: &mut EntityCommands, _node: &mut GodotNode| {
73            entity.insert(C::default());
74        });
75        self.components.push((type_id, inserter));
76    }
77
78    /// Register a component type with custom initialization logic
79    pub fn register_with_init<C, F>(&mut self, init_fn: F)
80    where
81        C: Component,
82        F: Fn(&mut EntityCommands, &mut GodotNode) + Send + Sync + 'static,
83    {
84        let type_id = TypeId::of::<C>();
85
86        // Check if already registered
87        if self.components.iter().any(|(id, _)| *id == type_id) {
88            return;
89        }
90
91        let inserter = Box::new(init_fn);
92        self.components.push((type_id, inserter));
93    }
94
95    /// Add all registered components to an entity
96    pub fn add_to_entity(&self, entity: &mut EntityCommands, node: &mut GodotNode) {
97        for (_, inserter) in &self.components {
98            inserter(entity, node);
99        }
100    }
101}
102
103/// Extension trait for App to register scene tree components
104pub trait AppSceneTreeExt {
105    /// Register a component to be added to all scene tree entities with default value
106    fn register_scene_tree_component<C>(&mut self) -> &mut Self
107    where
108        C: Component + Default;
109
110    /// Register a component with custom initialization logic that has access to the Godot node
111    fn register_scene_tree_component_with_init<C, F>(&mut self, init_fn: F) -> &mut Self
112    where
113        C: Component,
114        F: Fn(&mut EntityCommands, &mut GodotNode) + Send + Sync + 'static;
115}
116
117impl AppSceneTreeExt for App {
118    fn register_scene_tree_component<C>(&mut self) -> &mut Self
119    where
120        C: Component + Default,
121    {
122        // Get or create the registry
123        if !self
124            .world()
125            .contains_resource::<SceneTreeComponentRegistry>()
126        {
127            self.world_mut()
128                .init_resource::<SceneTreeComponentRegistry>();
129        }
130
131        self.world_mut()
132            .resource_mut::<SceneTreeComponentRegistry>()
133            .register::<C>();
134
135        self
136    }
137
138    fn register_scene_tree_component_with_init<C, F>(&mut self, init_fn: F) -> &mut Self
139    where
140        C: Component,
141        F: Fn(&mut EntityCommands, &mut GodotNode) + Send + Sync + 'static,
142    {
143        // Get or create the registry
144        if !self
145            .world()
146            .contains_resource::<SceneTreeComponentRegistry>()
147        {
148            self.world_mut()
149                .init_resource::<SceneTreeComponentRegistry>();
150        }
151
152        self.world_mut()
153            .resource_mut::<SceneTreeComponentRegistry>()
154            .register_with_init::<C, F>(init_fn);
155
156        self
157    }
158}
159
160/// Minimal core plugin with only essential Godot-Bevy integration.
161/// This includes scene tree management, basic Bevy setup, and core resources.
162#[derive(Default)]
163pub struct GodotBaseCorePlugin;
164
165impl Plugin for GodotBaseCorePlugin {
166    fn build(&self, app: &mut App) {
167        app.add_plugins(bevy_time::TimePlugin)
168            .add_plugins(bevy_app::TaskPoolPlugin::default())
169            .add_plugins(bevy_diagnostic::FrameCountPlugin)
170            .add_plugins(bevy_diagnostic::DiagnosticsPlugin)
171            .init_resource::<PhysicsDelta>()
172            .init_non_send_resource::<GodotMainThread>()
173            .init_resource::<SceneTreeComponentRegistry>()
174            .add_observer(on_godot_node_handle_removed);
175
176        // Add the PhysicsUpdate schedule
177        app.add_schedule(Schedule::new(PrePhysicsUpdate));
178        app.add_schedule(Schedule::new(PhysicsUpdate));
179    }
180}
181
182/// SystemParam to keep track of an independent delta time
183///
184/// Not every system runs on a Bevy update and Bevy can be updated multiple
185/// during a "frame".
186#[derive(SystemParam)]
187#[deprecated(note = "Use PhysicsDelta instead")]
188pub struct SystemDeltaTimer<'w, 's> {
189    last_time: Local<'s, Option<Instant>>,
190    marker: PhantomData<&'w ()>,
191}
192
193#[allow(deprecated)]
194impl<'w, 's> SystemDeltaTimer<'w, 's> {
195    /// Returns the time passed since the last invocation
196    pub fn delta(&mut self) -> Duration {
197        let now = Instant::now();
198        let last_time = self.last_time.unwrap_or(now);
199
200        *self.last_time = Some(now);
201
202        now - last_time
203    }
204
205    pub fn delta_seconds(&mut self) -> f32 {
206        self.delta().as_secs_f32()
207    }
208
209    pub fn delta_seconds_f64(&mut self) -> f64 {
210        self.delta().as_secs_f64()
211    }
212}
213
214pub trait FindEntityByNameExt<T> {
215    fn find_entity_by_name(self, name: &str) -> Option<T>;
216}
217
218impl<'a, T: 'a, U> FindEntityByNameExt<T> for U
219where
220    U: Iterator<Item = (&'a Name, T)>,
221{
222    fn find_entity_by_name(mut self, name: &str) -> Option<T> {
223        self.find_map(|(ent_name, t)| (ent_name.as_str() == name).then_some(t))
224    }
225}
226
227/// Observer that automatically frees Godot nodes when GodotNodeHandle components are removed
228fn on_godot_node_handle_removed(
229    trigger: On<Remove, GodotNodeHandle>,
230    query: Query<&GodotNodeHandle>,
231    mut godot: GodotAccess,
232) {
233    if let Ok(handle) = query.get(trigger.event_target())
234        && let Some(mut node) = godot.try_get::<Node>(*handle)
235    {
236        debug!(
237            "Freeing Godot node with instance_id {:?}",
238            handle.instance_id()
239        );
240        node.queue_free();
241    }
242}