godot_bevy/plugins/
core.rs1#![allow(deprecated)] use 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#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
18pub struct PrePhysicsUpdate;
19
20#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
23pub struct PhysicsUpdate;
24
25#[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
48type ComponentInserter = Box<dyn Fn(&mut EntityCommands, &mut GodotNode) + Send + Sync>;
50
51#[derive(Resource, Default)]
53pub struct SceneTreeComponentRegistry {
54 components: Vec<(TypeId, ComponentInserter)>,
57}
58
59impl SceneTreeComponentRegistry {
60 pub fn register<C>(&mut self)
62 where
63 C: Component + Default,
64 {
65 let type_id = TypeId::of::<C>();
66
67 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 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 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 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
103pub trait AppSceneTreeExt {
105 fn register_scene_tree_component<C>(&mut self) -> &mut Self
107 where
108 C: Component + Default;
109
110 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 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 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#[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 app.add_schedule(Schedule::new(PrePhysicsUpdate));
178 app.add_schedule(Schedule::new(PhysicsUpdate));
179 }
180}
181
182#[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 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
227fn 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}