From 0c953880b372cdb6de7ad639de412e14b9b05652 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Tue, 15 Jul 2025 17:39:45 -0700 Subject: [PATCH] Write tests for scene hot reloading. --- crates/bevy_scene/src/lib.rs | 296 +++++++++++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index a6874f9721..9e6fe16d1a 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -114,3 +114,299 @@ impl Plugin for ScenePlugin { impl Plugin for ScenePlugin { fn build(&self, _: &mut App) {} } + +#[cfg(test)] +mod tests { + use bevy_app::App; + use bevy_asset::{AssetPlugin, Assets}; + use bevy_ecs::{ + component::Component, + hierarchy::{ChildOf, Children}, + reflect::{AppTypeRegistry, ReflectComponent}, + world::World, + }; + use bevy_reflect::Reflect; + + use crate::{ + DynamicScene, DynamicSceneBuilder, DynamicSceneRoot, Scene, ScenePlugin, SceneRoot, + }; + + #[derive(Component, Reflect, PartialEq, Debug)] + #[reflect(Component)] + struct Circle { + radius: f32, + } + + #[derive(Component, Reflect, PartialEq, Debug)] + #[reflect(Component)] + struct Rectangle { + width: f32, + height: f32, + } + + #[derive(Component, Reflect, PartialEq, Debug)] + #[reflect(Component)] + struct Triangle { + base: f32, + height: f32, + } + + #[derive(Component, Reflect)] + #[reflect(Component)] + struct FinishLine; + + #[test] + fn scene_spawns_and_respawns_after_change() { + let mut app = App::new(); + + app.add_plugins((AssetPlugin::default(), ScenePlugin)) + .register_type::() + .register_type::() + .register_type::() + .register_type::(); + + let scene_handle = app + .world_mut() + .resource_mut::>() + .reserve_handle(); + + let scene_entity = app.world_mut().spawn(SceneRoot(scene_handle.clone())).id(); + app.update(); + + assert!(app.world().entity(scene_entity).get::().is_none()); + + let mut scene_1 = Scene { + world: World::new(), + }; + let root = scene_1.world.spawn_empty().id(); + scene_1.world.spawn(( + Rectangle { + width: 10.0, + height: 5.0, + }, + FinishLine, + ChildOf(root), + )); + scene_1.world.spawn((Circle { radius: 7.0 }, ChildOf(root))); + + app.world_mut() + .resource_mut::>() + .insert(&scene_handle, scene_1); + + app.update(); + + let child_root = app + .world() + .entity(scene_entity) + .get::() + .and_then(|children| children.first().cloned()) + .expect("There should be exactly one child on the scene root"); + let children = app + .world() + .entity(child_root) + .get::() + .expect("The child of the scene root should itself have 2 children"); + assert_eq!(children.len(), 2); + + let finish_line = app.world().entity(children[0]); + assert_eq!(finish_line.archetype().component_count(), 3); + let (rectangle, _, child_of) = + finish_line.components::<(&Rectangle, &FinishLine, &ChildOf)>(); + assert_eq!( + rectangle, + &Rectangle { + width: 10.0, + height: 5.0, + } + ); + assert_eq!(child_of.0, child_root); + + let circle = app.world().entity(children[1]); + assert_eq!(circle.archetype().component_count(), 2); + let (circle, child_of) = circle.components::<(&Circle, &ChildOf)>(); + assert_eq!(circle, &Circle { radius: 7.0 }); + assert_eq!(child_of.0, child_root); + + // Now that we know our scene contains exactly what we expect, we will change the scene + // asset and ensure it contains the new scene results. + + let mut scene_2 = Scene { + world: World::new(), + }; + let root = scene_2.world.spawn_empty().id(); + scene_2.world.spawn(( + Triangle { + base: 1.0, + height: 2.0, + }, + ChildOf(root), + )); + + app.world_mut() + .resource_mut::>() + .insert(&scene_handle, scene_2); + + app.update(); + app.update(); + + let child_root = app + .world() + .entity(scene_entity) + .get::() + .and_then(|children| children.first().cloned()) + .expect("There should be exactly one child on the scene root"); + let children = app + .world() + .entity(child_root) + .get::() + .expect("The child of the scene root should itself have 2 children"); + assert_eq!(children.len(), 1); + + let triangle = app.world().entity(children[0]); + assert_eq!(triangle.archetype().component_count(), 2); + let (triangle, child_of) = triangle.components::<(&Triangle, &ChildOf)>(); + assert_eq!( + triangle, + &Triangle { + base: 1.0, + height: 2.0, + } + ); + assert_eq!(child_of.0, child_root); + } + + #[test] + fn dynamic_scene_spawns_and_respawns_after_change() { + let mut app = App::new(); + + app.add_plugins((AssetPlugin::default(), ScenePlugin)) + .register_type::() + .register_type::() + .register_type::() + .register_type::(); + + let scene_handle = app + .world_mut() + .resource_mut::>() + .reserve_handle(); + + let scene_entity = app + .world_mut() + .spawn(DynamicSceneRoot(scene_handle.clone())) + .id(); + app.update(); + + assert!(app.world().entity(scene_entity).get::().is_none()); + + let create_dynamic_scene = |mut scene: Scene, world: &World| { + scene + .world + .insert_resource(world.resource::().clone()); + DynamicSceneBuilder::from_world(&scene.world) + .extract_entities(scene.world.iter_entities().map(|entity| entity.id())) + .build() + }; + + let mut scene_1 = Scene { + world: World::new(), + }; + let root = scene_1.world.spawn_empty().id(); + scene_1.world.spawn(( + Rectangle { + width: 10.0, + height: 5.0, + }, + FinishLine, + ChildOf(root), + )); + scene_1.world.spawn((Circle { radius: 7.0 }, ChildOf(root))); + + let scene_1 = create_dynamic_scene(scene_1, app.world()); + app.world_mut() + .resource_mut::>() + .insert(&scene_handle, scene_1); + + app.update(); + + let child_root = app + .world() + .entity(scene_entity) + .get::() + .and_then(|children| children.first().cloned()) + .expect("There should be exactly one child on the scene root"); + let children = app + .world() + .entity(child_root) + .get::() + .expect("The child of the scene root should itself have 2 children"); + assert_eq!(children.len(), 2); + + let finish_line = app.world().entity(children[0]); + assert_eq!(finish_line.archetype().component_count(), 3); + let (rectangle, _, child_of) = + finish_line.components::<(&Rectangle, &FinishLine, &ChildOf)>(); + assert_eq!( + rectangle, + &Rectangle { + width: 10.0, + height: 5.0, + } + ); + assert_eq!(child_of.0, child_root); + + let circle = app.world().entity(children[1]); + assert_eq!(circle.archetype().component_count(), 2); + let (circle, child_of) = circle.components::<(&Circle, &ChildOf)>(); + assert_eq!(circle, &Circle { radius: 7.0 }); + assert_eq!(child_of.0, child_root); + + // Now that we know our scene contains exactly what we expect, we will change the scene + // asset and ensure it contains the new scene results. + + let mut scene_2 = Scene { + world: World::new(), + }; + let root = scene_2.world.spawn_empty().id(); + scene_2.world.spawn(( + Triangle { + base: 1.0, + height: 2.0, + }, + ChildOf(root), + )); + + let scene_2 = create_dynamic_scene(scene_2, app.world()); + + app.world_mut() + .resource_mut::>() + .insert(&scene_handle, scene_2); + + app.update(); + app.update(); + + let child_root = app + .world() + .entity(scene_entity) + .get::() + .and_then(|children| children.first().cloned()) + .expect("There should be exactly one child on the scene root"); + let children = app + .world() + .entity(child_root) + .get::() + .expect("The child of the scene root should itself have 2 children"); + assert_eq!(children.len(), 1); + + let triangle = app.world().entity(children[0]); + assert_eq!(triangle.archetype().component_count(), 2); + let (triangle, child_of) = triangle.components::<(&Triangle, &ChildOf)>(); + assert_eq!( + triangle, + &Triangle { + base: 1.0, + height: 2.0, + } + ); + assert_eq!(child_of.0, child_root); + } +}