goud_engine/ecs/app/physics_plugins.rs
1//! Physics plugins for 2D and 3D ECS integration.
2//!
3//! These plugins register the necessary resources for physics synchronization.
4//! The physics step systems must be added separately because they require
5//! ownership of a [`PhysicsProvider`] instance, and [`Plugin::build`] takes
6//! `&self` (preventing move semantics).
7//!
8//! # Usage
9//!
10//! ```rust,ignore
11//! use goud_engine::ecs::app::{App, PhysicsPlugin2D};
12//! use goud_engine::ecs::schedule::CoreStage;
13//! use goud_engine::ecs::systems::PhysicsStepSystem2D;
14//! use goud_engine::core::providers::impls::NullPhysicsProvider;
15//!
16//! let mut app = App::new();
17//! app.add_plugin(PhysicsPlugin2D);
18//! app.add_system(
19//! CoreStage::Update,
20//! PhysicsStepSystem2D::new(Box::new(NullPhysicsProvider::new())),
21//! );
22//! ```
23
24use super::plugin::Plugin;
25use super::App;
26use crate::ecs::systems::physics_sync_2d::PhysicsHandleMap2D;
27use crate::ecs::systems::physics_sync_3d::PhysicsHandleMap3D;
28
29/// Plugin that registers 2D physics resources.
30///
31/// Inserts a default [`PhysicsHandleMap2D`] resource into the world.
32/// After adding this plugin, add a [`PhysicsStepSystem2D`] to the desired
33/// stage (typically [`CoreStage::Update`]).
34///
35/// [`PhysicsStepSystem2D`]: crate::ecs::systems::PhysicsStepSystem2D
36/// [`CoreStage::Update`]: crate::ecs::schedule::CoreStage::Update
37#[derive(Debug, Default, Clone)]
38pub struct PhysicsPlugin2D;
39
40impl Plugin for PhysicsPlugin2D {
41 fn build(&self, app: &mut App) {
42 app.insert_resource(PhysicsHandleMap2D::default());
43 }
44
45 fn name(&self) -> &'static str {
46 "PhysicsPlugin2D"
47 }
48}
49
50/// Plugin that registers 3D physics resources.
51///
52/// Inserts a default [`PhysicsHandleMap3D`] resource into the world.
53/// After adding this plugin, add a [`PhysicsStepSystem3D`] to the desired
54/// stage (typically [`CoreStage::Update`]).
55///
56/// [`PhysicsStepSystem3D`]: crate::ecs::systems::PhysicsStepSystem3D
57/// [`CoreStage::Update`]: crate::ecs::schedule::CoreStage::Update
58#[derive(Debug, Default, Clone)]
59pub struct PhysicsPlugin3D;
60
61impl Plugin for PhysicsPlugin3D {
62 fn build(&self, app: &mut App) {
63 app.insert_resource(PhysicsHandleMap3D::default());
64 }
65
66 fn name(&self) -> &'static str {
67 "PhysicsPlugin3D"
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn test_physics_plugin_2d_build() {
77 let mut app = App::new();
78 app.add_plugin(PhysicsPlugin2D);
79 assert!(app.world().get_resource::<PhysicsHandleMap2D>().is_some());
80 }
81
82 #[test]
83 fn test_physics_plugin_2d_name() {
84 let plugin = PhysicsPlugin2D;
85 assert_eq!(plugin.name(), "PhysicsPlugin2D");
86 }
87
88 #[test]
89 fn test_physics_plugin_3d_build() {
90 let mut app = App::new();
91 app.add_plugin(PhysicsPlugin3D);
92 assert!(app.world().get_resource::<PhysicsHandleMap3D>().is_some());
93 }
94
95 #[test]
96 fn test_physics_plugin_3d_name() {
97 let plugin = PhysicsPlugin3D;
98 assert_eq!(plugin.name(), "PhysicsPlugin3D");
99 }
100
101 #[test]
102 fn test_both_plugins_can_coexist() {
103 let mut app = App::new();
104 app.add_plugin(PhysicsPlugin2D);
105 app.add_plugin(PhysicsPlugin3D);
106 assert!(app.world().get_resource::<PhysicsHandleMap2D>().is_some());
107 assert!(app.world().get_resource::<PhysicsHandleMap3D>().is_some());
108 }
109
110 #[test]
111 fn test_duplicate_plugin_ignored() {
112 let mut app = App::new();
113 app.add_plugin(PhysicsPlugin2D);
114 // Second add should be silently ignored (no panic).
115 app.add_plugin(PhysicsPlugin2D);
116 }
117}