From 065a94aca8989b346adc4190f5922e8b50768628 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 29 May 2020 12:56:32 -0700 Subject: [PATCH] scene: hot scene reloading. update load_scene example --- assets/scene/load_scene_example.scn | 26 ++-- crates/bevy_asset/src/asset_server.rs | 118 ++++++++++-------- crates/bevy_asset/src/filesystem_watcher.rs | 2 +- .../legion_core/src/guid_entity_allocator.rs | 10 +- crates/bevy_scene/src/lib.rs | 15 ++- .../src/{scene_loader.rs => loaded_scenes.rs} | 0 crates/bevy_scene/src/scene.rs | 34 ----- crates/bevy_scene/src/scene_spawner.rs | 118 ++++++++++++++++++ examples/scene/load_scene.rs | 87 +++++++++---- src/prelude.rs | 2 +- 10 files changed, 273 insertions(+), 139 deletions(-) rename crates/bevy_scene/src/{scene_loader.rs => loaded_scenes.rs} (100%) create mode 100644 crates/bevy_scene/src/scene_spawner.rs diff --git a/assets/scene/load_scene_example.scn b/assets/scene/load_scene_example.scn index c82fff45df..6569df5c1b 100644 --- a/assets/scene/load_scene_example.scn +++ b/assets/scene/load_scene_example.scn @@ -1,32 +1,32 @@ [ - - Entity( - id: 510837483, + ( + entity: 328997855, components: [ - { - "type": "load_scene::ComponentA", + "type": "ComponentA", "map": { "x": 3.0, "y": 4.0, }, - }, ], + }, + ], ), - Entity( - id: 1069398672, + ( + entity: 404566393, components: [ - { - "type": "load_scene::ComponentA", + "type": "ComponentA", "map": { "x": 1.0, "y": 2.0, }, }, { - "type": "load_scene::ComponentB", + "type": "ComponentB", "map": { "value": "hello", }, - }, ], - ),] \ No newline at end of file + }, + ], + ), +] \ No newline at end of file diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index 27a2bff273..d8eac6553d 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -29,7 +29,7 @@ pub enum AssetServerError { #[error("Encountered an io error.")] Io(#[from] io::Error), #[error("Failed to watch asset folder.")] - AssetFolderWatchError { path: PathBuf }, + AssetWatchError { path: PathBuf }, } struct LoaderThread { @@ -49,7 +49,7 @@ pub struct AssetServer { extension_to_loader_index: HashMap, path_to_handle: RwLock>, #[cfg(feature = "filesystem_watcher")] - filesystem_watcher: Option, + filesystem_watcher: Arc>>, } impl Default for AssetServer { @@ -64,7 +64,7 @@ impl Default for AssetServer { extension_to_loader_index: HashMap::new(), path_to_handle: RwLock::new(HashMap::default()), #[cfg(feature = "filesystem_watcher")] - filesystem_watcher: None, + filesystem_watcher: Arc::new(RwLock::new(None)), } } } @@ -74,7 +74,7 @@ impl AssetServer { where T: AssetLoadRequestHandler, { - let mut asset_handlers = self.asset_handlers.write().expect("RwLock poisoned"); + let mut asset_handlers = self.asset_handlers.write().unwrap(); let handler_index = asset_handlers.len(); for extension in asset_handler.extensions().iter() { self.extension_to_handler_index @@ -103,10 +103,6 @@ impl AssetServer { pub fn load_asset_folder>(&mut self, path: P) -> Result<(), AssetServerError> { let root_path = self.get_root_path()?; let asset_folder = root_path.join(path); - - #[cfg(feature = "filesystem_watcher")] - Self::watch_folder_for_changes(&mut self.filesystem_watcher, &asset_folder)?; - self.load_assets_in_folder_recursive(&asset_folder)?; self.asset_folders.push(asset_folder); Ok(()) @@ -115,34 +111,36 @@ impl AssetServer { pub fn get_handle>(&self, path: P) -> Option> { self.path_to_handle .read() - .expect("RwLock poisoned") + .unwrap() .get(path.as_ref()) .map(|h| Handle::from(*h)) } #[cfg(feature = "filesystem_watcher")] - fn watch_folder_for_changes>( + fn watch_path_for_changes>( filesystem_watcher: &mut Option, path: P, ) -> Result<(), AssetServerError> { if let Some(watcher) = filesystem_watcher { - watcher.watch_folder(&path).map_err(|_error| { - AssetServerError::AssetFolderWatchError { + watcher + .watch(&path) + .map_err(|_error| AssetServerError::AssetWatchError { path: path.as_ref().to_owned(), - } - })?; + })?; } Ok(()) } #[cfg(feature = "filesystem_watcher")] - pub fn watch_for_changes(&mut self) -> Result<(), AssetServerError> { - let _ = self - .filesystem_watcher - .get_or_insert_with(|| FilesystemWatcher::default()); - for asset_folder in self.asset_folders.iter() { - Self::watch_folder_for_changes(&mut self.filesystem_watcher, asset_folder)?; + pub fn watch_for_changes(&self) -> Result<(), AssetServerError> { + let mut filesystem_watcher = self.filesystem_watcher.write().unwrap(); + + let _ = filesystem_watcher.get_or_insert_with(|| FilesystemWatcher::default()); + // watch current files + let path_to_handle = self.path_to_handle.read().unwrap(); + for asset_path in path_to_handle.keys() { + Self::watch_path_for_changes(&mut filesystem_watcher, asset_path)?; } Ok(()) @@ -153,34 +151,11 @@ impl AssetServer { use notify::event::{Event, EventKind, ModifyKind}; let mut changed = HashSet::new(); loop { - if let Some(ref filesystem_watcher) = asset_server.filesystem_watcher { + let result = if let Some(filesystem_watcher) = + asset_server.filesystem_watcher.read().unwrap().as_ref() + { match filesystem_watcher.receiver.try_recv() { - Ok(result) => { - let event = result.unwrap(); - match event { - Event { - kind: EventKind::Modify(ModifyKind::Data(_)), - paths, - .. - } => { - for path in paths.iter() { - if !changed.contains(path) { - let root_path = asset_server.get_root_path().unwrap(); - let relative_path = path.strip_prefix(root_path).unwrap(); - match asset_server.load_untyped(relative_path) { - Ok(_) => {} - Err(AssetServerError::AssetLoadError(error)) => { - panic!("{:?}", error) - } - Err(_) => {} - } - } - } - changed.extend(paths); - } - _ => {} - } - } + Ok(result) => result, Err(TryRecvError::Empty) => { break; } @@ -188,6 +163,31 @@ impl AssetServer { } } else { break; + }; + + let event = result.unwrap(); + match event { + Event { + kind: EventKind::Modify(ModifyKind::Data(_)), + paths, + .. + } => { + for path in paths.iter() { + if !changed.contains(path) { + let root_path = asset_server.get_root_path().unwrap(); + let relative_path = path.strip_prefix(root_path).unwrap(); + match asset_server.load_untyped(relative_path) { + Ok(_) => {} + Err(AssetServerError::AssetLoadError(error)) => { + panic!("{:?}", error) + } + Err(_) => {} + } + } + } + changed.extend(paths); + } + _ => {} } } } @@ -252,7 +252,7 @@ impl AssetServer { .expect("Extension should be a valid string."), ) { let handle_id = { - let mut path_to_handle = self.path_to_handle.write().expect("RwLock poisoned"); + let mut path_to_handle = self.path_to_handle.write().unwrap(); if let Some(handle_id) = path_to_handle.get(path) { *handle_id } else { @@ -267,6 +267,11 @@ impl AssetServer { path: path.to_owned(), handler_index: *index, }); + + // TODO: watching each asset explicitly is a simpler implementation, its possible it would be more efficient to watch + // folders instead (when possible) + #[cfg(feature = "filesystem_watcher")] + Self::watch_path_for_changes(&mut self.filesystem_watcher.write().unwrap(), path)?; Ok(handle_id) } else { Err(AssetServerError::MissingAssetHandler) @@ -277,7 +282,7 @@ impl AssetServer { } fn send_request_to_loader_thread(&self, load_request: LoadRequest) { - let mut loader_threads = self.loader_threads.write().expect("RwLock poisoned"); + let mut loader_threads = self.loader_threads.write().unwrap(); if loader_threads.len() < self.max_loader_threads { let loader_thread = LoaderThread { requests: Arc::new(RwLock::new(vec![load_request])), @@ -288,9 +293,9 @@ impl AssetServer { } else { let most_free_thread = loader_threads .iter() - .min_by_key(|l| l.requests.read().expect("RwLock poisoned").len()) + .min_by_key(|l| l.requests.read().unwrap().len()) .unwrap(); - let mut requests = most_free_thread.requests.write().expect("RwLock poisoned"); + let mut requests = most_free_thread.requests.write().unwrap(); requests.push(load_request); // if most free thread only has one reference, the thread as spun down. if so, we need to spin it back up! if Arc::strong_count(&most_free_thread.requests) == 1 { @@ -309,7 +314,7 @@ impl AssetServer { thread::spawn(move || { loop { let request = { - let mut current_requests = requests.write().expect("RwLock poisoned"); + let mut current_requests = requests.write().unwrap(); if current_requests.len() == 0 { // if there are no requests, spin down the thread break; @@ -318,7 +323,7 @@ impl AssetServer { current_requests.pop().unwrap() }; - let handlers = request_handlers.read().expect("RwLock poisoned"); + let handlers = request_handlers.read().unwrap(); let request_handler = &handlers[request.handler_index]; request_handler.handle_request(&request); } @@ -338,8 +343,11 @@ impl AssetServer { let child_path = entry.path(); if !child_path.is_dir() { let relative_child_path = child_path.strip_prefix(&root_path).unwrap(); - let _ = - self.load_untyped(relative_child_path.to_str().expect("Path should be a valid string")); + let _ = self.load_untyped( + relative_child_path + .to_str() + .expect("Path should be a valid string"), + ); } else { self.load_assets_in_folder_recursive(&child_path)?; } diff --git a/crates/bevy_asset/src/filesystem_watcher.rs b/crates/bevy_asset/src/filesystem_watcher.rs index d89bc03ea9..386d49e370 100644 --- a/crates/bevy_asset/src/filesystem_watcher.rs +++ b/crates/bevy_asset/src/filesystem_watcher.rs @@ -20,7 +20,7 @@ impl Default for FilesystemWatcher { } impl FilesystemWatcher { - pub fn watch_folder>(&mut self, path: P) -> Result<()> { + pub fn watch>(&mut self, path: P) -> Result<()> { self.watcher.watch(path, RecursiveMode::Recursive) } } diff --git a/crates/bevy_legion/legion_core/src/guid_entity_allocator.rs b/crates/bevy_legion/legion_core/src/guid_entity_allocator.rs index b0e9e52320..bb8ace9445 100644 --- a/crates/bevy_legion/legion_core/src/guid_entity_allocator.rs +++ b/crates/bevy_legion/legion_core/src/guid_entity_allocator.rs @@ -1,15 +1,15 @@ -use crate::entity::Entity; +use crate::entity::{EntityIndex, Entity}; use parking_lot::RwLock; use std::{collections::{VecDeque, HashSet}, num::Wrapping, sync::Arc}; #[derive(Default, Debug, Clone)] pub struct GuidEntityAllocator { - entities: Arc>>, + entities: Arc>>, next_ids: Arc>>, } impl GuidEntityAllocator { - pub fn is_alive(&self, entity: Entity) -> bool { self.entities.read().contains(&entity) } + pub fn is_alive(&self, entity: Entity) -> bool { self.entities.read().contains(&entity.index()) } pub fn push_next_ids(&self, ids: impl Iterator) { self.next_ids.write().extend(ids); @@ -23,7 +23,7 @@ impl GuidEntityAllocator { Entity::new(rand::random::(), Wrapping(1)) }; - self.entities.write().insert(entity); + self.entities.write().insert(entity.index()); entity } @@ -33,7 +33,7 @@ impl GuidEntityAllocator { } pub(crate) fn delete_entity(&self, entity: Entity) -> bool { - self.entities.write().remove(&entity) + self.entities.write().remove(&entity.index()) } pub(crate) fn delete_all_entities(&self) { self.entities.write().clear(); } diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 07b0873ac2..c61b5a25eb 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -1,19 +1,26 @@ +mod loaded_scenes; mod scene; -mod scene_loader; +mod scene_spawner; pub mod serde; +pub use loaded_scenes::*; pub use scene::*; -pub use scene_loader::*; +pub use scene_spawner::*; -use bevy_app::{AppBuilder, AppPlugin}; +use bevy_app::{stage, AppBuilder, AppPlugin}; use bevy_asset::AddAsset; #[derive(Default)] pub struct ScenePlugin; +pub const SCENE_STAGE: &str = "scene"; + impl AppPlugin for ScenePlugin { fn build(&self, app: &mut AppBuilder) { app.add_asset::() - .add_asset_loader::(); + .add_asset_loader::() + .init_resource::() + .add_stage_after(stage::EVENT_UPDATE, SCENE_STAGE) + .add_system_to_stage(SCENE_STAGE, scene_spawner_system); } } diff --git a/crates/bevy_scene/src/scene_loader.rs b/crates/bevy_scene/src/loaded_scenes.rs similarity index 100% rename from crates/bevy_scene/src/scene_loader.rs rename to crates/bevy_scene/src/loaded_scenes.rs diff --git a/crates/bevy_scene/src/scene.rs b/crates/bevy_scene/src/scene.rs index 367a98f232..efd49bad35 100644 --- a/crates/bevy_scene/src/scene.rs +++ b/crates/bevy_scene/src/scene.rs @@ -4,7 +4,6 @@ use bevy_property::{PropertyTypeRegistry, DynamicProperties}; use legion::prelude::{Resources, World}; use serde::Serialize; use std::num::Wrapping; -use thiserror::Error; use crate::serde::SceneSerializer; #[derive(Default)] @@ -17,12 +16,6 @@ pub struct Entity { pub components: Vec, } -#[derive(Error, Debug)] -pub enum SceneAddError { - #[error("Scene contains an unregistered component.")] - UnregisteredComponent { type_name: String }, -} - impl Scene { pub fn from_world(world: &World, component_registry: &ComponentRegistry) -> Self { let mut scene = Scene::default(); @@ -63,33 +56,6 @@ impl Scene { scene } - pub fn add_to_world( - &self, - world: &mut World, - resources: &Resources, - component_registry: &ComponentRegistry, - ) -> Result<(), SceneAddError> { - world.entity_allocator.push_next_ids( - self.entities - .iter() - .map(|e| legion::prelude::Entity::new(e.entity, Wrapping(1))), - ); - for scene_entity in self.entities.iter() { - // TODO: use EntityEntry when legion refactor is finished - let entity = world.insert((), vec![()])[0]; - for component in scene_entity.components.iter() { - let component_registration = component_registry - .get_with_name(&component.type_name) - .ok_or_else(|| SceneAddError::UnregisteredComponent { - type_name: component.type_name.to_string(), - })?; - component_registration.add_component_to_entity(world, resources, entity, component); - } - } - - Ok(()) - } - // TODO: move to AssetSaver when it is implemented pub fn serialize_ron(&self, registry: &PropertyTypeRegistry) -> Result { let pretty_config = ron::ser::PrettyConfig::default() diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs new file mode 100644 index 0000000000..26c490defd --- /dev/null +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -0,0 +1,118 @@ +use crate::Scene; +use bevy_app::{EventReader, Events, FromResources, GetEventReader}; +use bevy_asset::{AssetEvent, Assets, Handle}; +use bevy_type_registry::TypeRegistry; +use legion::prelude::{Entity, Resources, World}; +use std::{ + collections::{HashMap, HashSet}, + num::Wrapping, +}; +use thiserror::Error; +pub struct SceneSpawner { + loaded_scene_entities: HashMap, Vec>, + scene_asset_event_reader: EventReader>, + scenes_to_spawn: Vec>, + scenes_to_load: Vec>, +} + +impl FromResources for SceneSpawner { + fn from_resources(resources: &Resources) -> Self { + SceneSpawner { + scene_asset_event_reader: resources.get_event_reader::>(), + loaded_scene_entities: Default::default(), + scenes_to_spawn: Default::default(), + scenes_to_load: Default::default(), + } + } +} + +#[derive(Error, Debug)] +pub enum SceneSpawnError { + #[error("Scene contains an unregistered component.")] + UnregisteredComponent { type_name: String }, + #[error("Scene does not exist. Perhaps it is still loading?")] + NonExistentScene { handle: Handle }, +} + +impl SceneSpawner { + pub fn spawn(&mut self, scene: Handle) { + self.scenes_to_spawn.push(scene); + } + + pub fn load(&mut self, scene: Handle) { + self.scenes_to_load.push(scene); + } + + pub fn load_sync( + &mut self, + world: &mut World, + resources: &Resources, + scene_handle: Handle, + ) -> Result<(), SceneSpawnError> { + let type_registry = resources.get::().unwrap(); + let component_registry = type_registry.component.read().unwrap(); + let scenes = resources.get::>().unwrap(); + let scene = scenes + .get(&scene_handle) + .ok_or_else(|| SceneSpawnError::NonExistentScene { + handle: scene_handle, + })?; + + // TODO: this vec might not be needed + let mut entity_ids = Vec::with_capacity(scene.entities.len()); + for scene_entity in scene.entities.iter() { + // TODO: use EntityEntry when legion refactor is finished + let mut entity = Entity::new(scene_entity.entity, Wrapping(1)); + if world.get_entity_location(entity).is_none() { + world.entity_allocator.push_next_ids((&[entity]).iter().cloned()); + entity = world.insert((), vec![()])[0]; + } + entity_ids.push(entity); + for component in scene_entity.components.iter() { + let component_registration = component_registry + .get_with_name(&component.type_name) + .ok_or_else(|| SceneSpawnError::UnregisteredComponent { + type_name: component.type_name.to_string(), + })?; + component_registration.add_component_to_entity(world, resources, entity, component); + } + } + + self.loaded_scene_entities.insert(scene_handle, entity_ids); + Ok(()) + } + + pub fn load_queued_scenes(&mut self, world: &mut World, resources: &Resources) { + let scenes_to_load = self.scenes_to_load.drain(..).collect::>(); + let mut non_existent_scenes = Vec::new(); + for scene_handle in scenes_to_load { + match self.load_sync(world, resources, scene_handle) { + Ok(_) => {} + Err(SceneSpawnError::NonExistentScene { .. }) => { + non_existent_scenes.push(scene_handle) + } + Err(err) => panic!("{:?}", err), + } + } + + self.scenes_to_load = non_existent_scenes; + } +} + +pub fn scene_spawner_system(world: &mut World, resources: &mut Resources) { + let mut scene_spawner = resources.get_mut::().unwrap(); + let scene_asset_events = resources.get::>>().unwrap(); + + for event in scene_spawner + .scene_asset_event_reader + .iter(&scene_asset_events) + { + if let AssetEvent::Modified { handle } = event { + if scene_spawner.loaded_scene_entities.contains_key(handle) { + scene_spawner.load(*handle); + } + } + } + + scene_spawner.load_queued_scenes(world, resources); +} diff --git a/examples/scene/load_scene.rs b/examples/scene/load_scene.rs index 25a4b7314f..24f25ac391 100644 --- a/examples/scene/load_scene.rs +++ b/examples/scene/load_scene.rs @@ -5,22 +5,23 @@ fn main() { .add_default_plugins() // Registering components informs Bevy that they exist. This allows them to be used when loading scenes // This step is only required if you want to load your components from scene files. - // Unregistered components can still be used in your code, but they won't be serialized / deserialized. + // Unregistered components can still be used in your code, but they will be ignored during scene save/load. // In the future registering components will also make them usable from the Bevy editor. // The core Bevy plugins already register their components, so you only need this step for custom components. .register_component::() .register_component::() .add_startup_system(save_scene_system) - .add_startup_system(load_scene_system) + .add_startup_system(load_scene_system.system()) + .add_system(print_system.system()) .run(); } // Registered components must implement the `Properties` and `FromResources` traits. // The `Properties` trait enables serialization, deserialization, dynamic property access, and change detection. // `Properties` enable a bunch of cool behaviors, so its worth checking out the dedicated `properties.rs` example. -// The `FromResources` trait determines how your component is constructed. -// For simple use cases you can just implement the `Default` trait (which automatically implements FromResources) -// The simplest registered component just needs these two derives: +// The `FromResources` trait determines how your component is constructed when it loads. For simple use cases you can just +// implement the `Default` trait (which automatically implements FromResources). The simplest registered component just needs +// these two derives: #[derive(Properties, Default)] struct ComponentA { pub x: f32, @@ -29,7 +30,7 @@ struct ComponentA { // Some components have fields that cannot (or should not) be written to scene files. These can be ignored with // the #[property(ignore)] attribute. This is also generally where the `FromResources` trait comes into play. -// This gives you access to your App's current ECS `Resources` when you construct your component. +// `FromResources` gives you access to your App's current ECS `Resources` when you construct your component. #[derive(Properties)] struct ComponentB { pub value: String, @@ -47,8 +48,59 @@ impl FromResources for ComponentB { } } -fn save_scene_system(world: &mut World, resources: &mut Resources) { - // Scenes can be created from any ECS World. +fn load_scene_system(asset_server: Res, mut scene_spawner: ResMut) { + // Scenes are loaded just like any other asset. + let scene_handle: Handle = asset_server + .load("assets/scene/load_scene_example.scn") + .unwrap(); + + // SceneSpawner can "spawn" scenes. "spawning" a scene creates a new instance of the scene in the World with new entity ids. + // This guarantees that it will not overwrite existing entities. + // scene_spawner.spawn(scene_handle); + + // SceneSpawner can also "load" scenes. "loading" a scene preserves the entity ids in the scene. + // In general, you should "spawn" scenes when you are dynamically composing your World and "load" scenes for things like game saves. + + scene_spawner.load(scene_handle); + + // This tells the AssetServer to watch for changes to assets. + // It enables our scenes to automatically reload in game when we modify their files + asset_server.watch_for_changes().unwrap(); +} + +// Using SceneSpawner spawn() and load() queues them up to be added to the World at the beginning of the next update. However if +// you need scenes to load immediately, you can use the following approach. But be aware that this takes full control of the ECS world +// and therefore blocks other parallel systems from executing until it finishes. In most cases you should use the SceneSpawner +// spawn() and load() methods. +#[allow(dead_code)] +fn load_scene_right_now_system(world: &mut World, resources: &mut Resources) { + let scene_handle: Handle = { + let asset_server = resources.get::().unwrap(); + let mut scenes = resources.get_mut::>().unwrap(); + asset_server + .load_sync(&mut scenes, "assets/scene/load_scene_example.scn") + .unwrap() + }; + let mut scene_spawner = resources.get_mut::().unwrap(); + scene_spawner.load_sync(world, resources, scene_handle).unwrap(); +} + +// This system prints all ComponentA components in our world. Try making a change to a ComponentA in load_scene_example.scn. +// You should immediately see the changes appear in the console. +fn print_system(world: &mut SubWorld, query: &mut Query>) { + println!("Current World State:"); + for (entity, component_a) in query.iter_entities(world) { + println!(" Entity({})", entity.index()); + println!( + " ComponentA: {{ x: {} y: {} }}\n", + component_a.x, component_a.y + ); + } +} + +fn save_scene_system(_world: &mut World, resources: &mut Resources) { + // Scenes can be created from any ECS World. You can either create a new one for the scene or use the current World. + let mut world = World::new(); world .build() .build_entity() @@ -62,7 +114,7 @@ fn save_scene_system(world: &mut World, resources: &mut Resources) { // The component registry resource contains information about all registered components. This is used to construct scenes. let type_registry = resources.get::().unwrap(); - let scene = Scene::from_world(world, &type_registry.component.read().unwrap()); + let scene = Scene::from_world(&world, &type_registry.component.read().unwrap()); // Scenes can be serialized like this: println!( @@ -74,20 +126,3 @@ fn save_scene_system(world: &mut World, resources: &mut Resources) { // TODO: save scene } - -fn load_scene_system(world: &mut World, resources: &mut Resources) { - let asset_server = resources.get::().unwrap(); - let mut scenes = resources.get_mut::>().unwrap(); - - // Scenes are loaded just like any other asset. - let scene_handle: Handle = asset_server - .load_sync(&mut scenes, "assets/scene/load_scene_example.scn") - .unwrap(); - let scene = scenes.get(&scene_handle).unwrap(); - - // Scenes can be added to any ECS World. Adding scenes also uses the component registry. - let type_registry = resources.get::().unwrap(); - scene - .add_to_world(world, resources, &type_registry.component.read().unwrap()) - .unwrap(); -} diff --git a/src/prelude.rs b/src/prelude.rs index c500a3f286..d13b613143 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -33,7 +33,7 @@ pub use crate::render::{ ActiveCamera, ActiveCamera2d, Camera, CameraType, Color, ColorSource, Renderable, }; #[cfg(feature = "scene")] -pub use crate::scene::Scene; +pub use crate::scene::{Scene, SceneSpawner}; #[cfg(feature = "text")] pub use crate::text::Font; #[cfg(feature = "transform")]