mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 06:00:20 +00:00
scene: hot scene reloading. update load_scene example
This commit is contained in:
parent
a7d9f8d0ff
commit
065a94aca8
10 changed files with 273 additions and 139 deletions
|
@ -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",
|
||||
},
|
||||
}, ],
|
||||
),]
|
||||
},
|
||||
],
|
||||
),
|
||||
]
|
|
@ -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<String, usize>,
|
||||
path_to_handle: RwLock<HashMap<PathBuf, HandleId>>,
|
||||
#[cfg(feature = "filesystem_watcher")]
|
||||
filesystem_watcher: Option<FilesystemWatcher>,
|
||||
filesystem_watcher: Arc<RwLock<Option<FilesystemWatcher>>>,
|
||||
}
|
||||
|
||||
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<P: AsRef<Path>>(&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<T, P: AsRef<Path>>(&self, path: P) -> Option<Handle<T>> {
|
||||
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<P: AsRef<Path>>(
|
||||
fn watch_path_for_changes<P: AsRef<Path>>(
|
||||
filesystem_watcher: &mut Option<FilesystemWatcher>,
|
||||
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)?;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ impl Default for FilesystemWatcher {
|
|||
}
|
||||
|
||||
impl FilesystemWatcher {
|
||||
pub fn watch_folder<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
|
||||
pub fn watch<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
|
||||
self.watcher.watch(path, RecursiveMode::Recursive)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<RwLock<HashSet<Entity>>>,
|
||||
entities: Arc<RwLock<HashSet<EntityIndex>>>,
|
||||
next_ids: Arc<RwLock<VecDeque<Entity>>>,
|
||||
}
|
||||
|
||||
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<Item = Entity>) {
|
||||
self.next_ids.write().extend(ids);
|
||||
|
@ -23,7 +23,7 @@ impl GuidEntityAllocator {
|
|||
Entity::new(rand::random::<u32>(), 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(); }
|
||||
|
|
|
@ -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::<Scene>()
|
||||
.add_asset_loader::<Scene, SceneLoader>();
|
||||
.add_asset_loader::<Scene, SceneLoader>()
|
||||
.init_resource::<SceneSpawner>()
|
||||
.add_stage_after(stage::EVENT_UPDATE, SCENE_STAGE)
|
||||
.add_system_to_stage(SCENE_STAGE, scene_spawner_system);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<DynamicProperties>,
|
||||
}
|
||||
|
||||
#[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<String, ron::Error> {
|
||||
let pretty_config = ron::ser::PrettyConfig::default()
|
||||
|
|
118
crates/bevy_scene/src/scene_spawner.rs
Normal file
118
crates/bevy_scene/src/scene_spawner.rs
Normal file
|
@ -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<Handle<Scene>, Vec<Entity>>,
|
||||
scene_asset_event_reader: EventReader<AssetEvent<Scene>>,
|
||||
scenes_to_spawn: Vec<Handle<Scene>>,
|
||||
scenes_to_load: Vec<Handle<Scene>>,
|
||||
}
|
||||
|
||||
impl FromResources for SceneSpawner {
|
||||
fn from_resources(resources: &Resources) -> Self {
|
||||
SceneSpawner {
|
||||
scene_asset_event_reader: resources.get_event_reader::<AssetEvent<Scene>>(),
|
||||
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<Scene> },
|
||||
}
|
||||
|
||||
impl SceneSpawner {
|
||||
pub fn spawn(&mut self, scene: Handle<Scene>) {
|
||||
self.scenes_to_spawn.push(scene);
|
||||
}
|
||||
|
||||
pub fn load(&mut self, scene: Handle<Scene>) {
|
||||
self.scenes_to_load.push(scene);
|
||||
}
|
||||
|
||||
pub fn load_sync(
|
||||
&mut self,
|
||||
world: &mut World,
|
||||
resources: &Resources,
|
||||
scene_handle: Handle<Scene>,
|
||||
) -> Result<(), SceneSpawnError> {
|
||||
let type_registry = resources.get::<TypeRegistry>().unwrap();
|
||||
let component_registry = type_registry.component.read().unwrap();
|
||||
let scenes = resources.get::<Assets<Scene>>().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::<Vec<_>>();
|
||||
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::<SceneSpawner>().unwrap();
|
||||
let scene_asset_events = resources.get::<Events<AssetEvent<Scene>>>().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);
|
||||
}
|
|
@ -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::<ComponentA>()
|
||||
.register_component::<ComponentB>()
|
||||
.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<AssetServer>, mut scene_spawner: ResMut<SceneSpawner>) {
|
||||
// Scenes are loaded just like any other asset.
|
||||
let scene_handle: Handle<Scene> = 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<Scene> = {
|
||||
let asset_server = resources.get::<AssetServer>().unwrap();
|
||||
let mut scenes = resources.get_mut::<Assets<Scene>>().unwrap();
|
||||
asset_server
|
||||
.load_sync(&mut scenes, "assets/scene/load_scene_example.scn")
|
||||
.unwrap()
|
||||
};
|
||||
let mut scene_spawner = resources.get_mut::<SceneSpawner>().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<Read<ComponentA>>) {
|
||||
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::<TypeRegistry>().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::<AssetServer>().unwrap();
|
||||
let mut scenes = resources.get_mut::<Assets<Scene>>().unwrap();
|
||||
|
||||
// Scenes are loaded just like any other asset.
|
||||
let scene_handle: Handle<Scene> = 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::<TypeRegistry>().unwrap();
|
||||
scene
|
||||
.add_to_world(world, resources, &type_registry.component.read().unwrap())
|
||||
.unwrap();
|
||||
}
|
||||
|
|
|
@ -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")]
|
||||
|
|
Loading…
Reference in a new issue