scene: hot scene reloading. update load_scene example
This commit is contained in:
parent
a7d9f8d0ff
commit
065a94aca8
@ -1,32 +1,32 @@
|
|||||||
[
|
[
|
||||||
|
(
|
||||||
Entity(
|
entity: 328997855,
|
||||||
id: 510837483,
|
|
||||||
components: [
|
components: [
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "load_scene::ComponentA",
|
"type": "ComponentA",
|
||||||
"map": {
|
"map": {
|
||||||
"x": 3.0,
|
"x": 3.0,
|
||||||
"y": 4.0,
|
"y": 4.0,
|
||||||
},
|
},
|
||||||
}, ],
|
},
|
||||||
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
(
|
||||||
id: 1069398672,
|
entity: 404566393,
|
||||||
components: [
|
components: [
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "load_scene::ComponentA",
|
"type": "ComponentA",
|
||||||
"map": {
|
"map": {
|
||||||
"x": 1.0,
|
"x": 1.0,
|
||||||
"y": 2.0,
|
"y": 2.0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "load_scene::ComponentB",
|
"type": "ComponentB",
|
||||||
"map": {
|
"map": {
|
||||||
"value": "hello",
|
"value": "hello",
|
||||||
},
|
},
|
||||||
}, ],
|
},
|
||||||
),]
|
],
|
||||||
|
),
|
||||||
|
]
|
@ -29,7 +29,7 @@ pub enum AssetServerError {
|
|||||||
#[error("Encountered an io error.")]
|
#[error("Encountered an io error.")]
|
||||||
Io(#[from] io::Error),
|
Io(#[from] io::Error),
|
||||||
#[error("Failed to watch asset folder.")]
|
#[error("Failed to watch asset folder.")]
|
||||||
AssetFolderWatchError { path: PathBuf },
|
AssetWatchError { path: PathBuf },
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LoaderThread {
|
struct LoaderThread {
|
||||||
@ -49,7 +49,7 @@ pub struct AssetServer {
|
|||||||
extension_to_loader_index: HashMap<String, usize>,
|
extension_to_loader_index: HashMap<String, usize>,
|
||||||
path_to_handle: RwLock<HashMap<PathBuf, HandleId>>,
|
path_to_handle: RwLock<HashMap<PathBuf, HandleId>>,
|
||||||
#[cfg(feature = "filesystem_watcher")]
|
#[cfg(feature = "filesystem_watcher")]
|
||||||
filesystem_watcher: Option<FilesystemWatcher>,
|
filesystem_watcher: Arc<RwLock<Option<FilesystemWatcher>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AssetServer {
|
impl Default for AssetServer {
|
||||||
@ -64,7 +64,7 @@ impl Default for AssetServer {
|
|||||||
extension_to_loader_index: HashMap::new(),
|
extension_to_loader_index: HashMap::new(),
|
||||||
path_to_handle: RwLock::new(HashMap::default()),
|
path_to_handle: RwLock::new(HashMap::default()),
|
||||||
#[cfg(feature = "filesystem_watcher")]
|
#[cfg(feature = "filesystem_watcher")]
|
||||||
filesystem_watcher: None,
|
filesystem_watcher: Arc::new(RwLock::new(None)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,7 +74,7 @@ impl AssetServer {
|
|||||||
where
|
where
|
||||||
T: AssetLoadRequestHandler,
|
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();
|
let handler_index = asset_handlers.len();
|
||||||
for extension in asset_handler.extensions().iter() {
|
for extension in asset_handler.extensions().iter() {
|
||||||
self.extension_to_handler_index
|
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> {
|
pub fn load_asset_folder<P: AsRef<Path>>(&mut self, path: P) -> Result<(), AssetServerError> {
|
||||||
let root_path = self.get_root_path()?;
|
let root_path = self.get_root_path()?;
|
||||||
let asset_folder = root_path.join(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.load_assets_in_folder_recursive(&asset_folder)?;
|
||||||
self.asset_folders.push(asset_folder);
|
self.asset_folders.push(asset_folder);
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -115,34 +111,36 @@ impl AssetServer {
|
|||||||
pub fn get_handle<T, P: AsRef<Path>>(&self, path: P) -> Option<Handle<T>> {
|
pub fn get_handle<T, P: AsRef<Path>>(&self, path: P) -> Option<Handle<T>> {
|
||||||
self.path_to_handle
|
self.path_to_handle
|
||||||
.read()
|
.read()
|
||||||
.expect("RwLock poisoned")
|
.unwrap()
|
||||||
.get(path.as_ref())
|
.get(path.as_ref())
|
||||||
.map(|h| Handle::from(*h))
|
.map(|h| Handle::from(*h))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "filesystem_watcher")]
|
#[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>,
|
filesystem_watcher: &mut Option<FilesystemWatcher>,
|
||||||
path: P,
|
path: P,
|
||||||
) -> Result<(), AssetServerError> {
|
) -> Result<(), AssetServerError> {
|
||||||
if let Some(watcher) = filesystem_watcher {
|
if let Some(watcher) = filesystem_watcher {
|
||||||
watcher.watch_folder(&path).map_err(|_error| {
|
watcher
|
||||||
AssetServerError::AssetFolderWatchError {
|
.watch(&path)
|
||||||
|
.map_err(|_error| AssetServerError::AssetWatchError {
|
||||||
path: path.as_ref().to_owned(),
|
path: path.as_ref().to_owned(),
|
||||||
}
|
})?;
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "filesystem_watcher")]
|
#[cfg(feature = "filesystem_watcher")]
|
||||||
pub fn watch_for_changes(&mut self) -> Result<(), AssetServerError> {
|
pub fn watch_for_changes(&self) -> Result<(), AssetServerError> {
|
||||||
let _ = self
|
let mut filesystem_watcher = self.filesystem_watcher.write().unwrap();
|
||||||
.filesystem_watcher
|
|
||||||
.get_or_insert_with(|| FilesystemWatcher::default());
|
let _ = filesystem_watcher.get_or_insert_with(|| FilesystemWatcher::default());
|
||||||
for asset_folder in self.asset_folders.iter() {
|
// watch current files
|
||||||
Self::watch_folder_for_changes(&mut self.filesystem_watcher, asset_folder)?;
|
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(())
|
Ok(())
|
||||||
@ -153,34 +151,11 @@ impl AssetServer {
|
|||||||
use notify::event::{Event, EventKind, ModifyKind};
|
use notify::event::{Event, EventKind, ModifyKind};
|
||||||
let mut changed = HashSet::new();
|
let mut changed = HashSet::new();
|
||||||
loop {
|
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() {
|
match filesystem_watcher.receiver.try_recv() {
|
||||||
Ok(result) => {
|
Ok(result) => 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);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(TryRecvError::Empty) => {
|
Err(TryRecvError::Empty) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -188,6 +163,31 @@ impl AssetServer {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
break;
|
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."),
|
.expect("Extension should be a valid string."),
|
||||||
) {
|
) {
|
||||||
let handle_id = {
|
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) {
|
if let Some(handle_id) = path_to_handle.get(path) {
|
||||||
*handle_id
|
*handle_id
|
||||||
} else {
|
} else {
|
||||||
@ -267,6 +267,11 @@ impl AssetServer {
|
|||||||
path: path.to_owned(),
|
path: path.to_owned(),
|
||||||
handler_index: *index,
|
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)
|
Ok(handle_id)
|
||||||
} else {
|
} else {
|
||||||
Err(AssetServerError::MissingAssetHandler)
|
Err(AssetServerError::MissingAssetHandler)
|
||||||
@ -277,7 +282,7 @@ impl AssetServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn send_request_to_loader_thread(&self, load_request: LoadRequest) {
|
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 {
|
if loader_threads.len() < self.max_loader_threads {
|
||||||
let loader_thread = LoaderThread {
|
let loader_thread = LoaderThread {
|
||||||
requests: Arc::new(RwLock::new(vec![load_request])),
|
requests: Arc::new(RwLock::new(vec![load_request])),
|
||||||
@ -288,9 +293,9 @@ impl AssetServer {
|
|||||||
} else {
|
} else {
|
||||||
let most_free_thread = loader_threads
|
let most_free_thread = loader_threads
|
||||||
.iter()
|
.iter()
|
||||||
.min_by_key(|l| l.requests.read().expect("RwLock poisoned").len())
|
.min_by_key(|l| l.requests.read().unwrap().len())
|
||||||
.unwrap();
|
.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);
|
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 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 {
|
if Arc::strong_count(&most_free_thread.requests) == 1 {
|
||||||
@ -309,7 +314,7 @@ impl AssetServer {
|
|||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
loop {
|
loop {
|
||||||
let request = {
|
let request = {
|
||||||
let mut current_requests = requests.write().expect("RwLock poisoned");
|
let mut current_requests = requests.write().unwrap();
|
||||||
if current_requests.len() == 0 {
|
if current_requests.len() == 0 {
|
||||||
// if there are no requests, spin down the thread
|
// if there are no requests, spin down the thread
|
||||||
break;
|
break;
|
||||||
@ -318,7 +323,7 @@ impl AssetServer {
|
|||||||
current_requests.pop().unwrap()
|
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];
|
let request_handler = &handlers[request.handler_index];
|
||||||
request_handler.handle_request(&request);
|
request_handler.handle_request(&request);
|
||||||
}
|
}
|
||||||
@ -338,8 +343,11 @@ impl AssetServer {
|
|||||||
let child_path = entry.path();
|
let child_path = entry.path();
|
||||||
if !child_path.is_dir() {
|
if !child_path.is_dir() {
|
||||||
let relative_child_path = child_path.strip_prefix(&root_path).unwrap();
|
let relative_child_path = child_path.strip_prefix(&root_path).unwrap();
|
||||||
let _ =
|
let _ = self.load_untyped(
|
||||||
self.load_untyped(relative_child_path.to_str().expect("Path should be a valid string"));
|
relative_child_path
|
||||||
|
.to_str()
|
||||||
|
.expect("Path should be a valid string"),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
self.load_assets_in_folder_recursive(&child_path)?;
|
self.load_assets_in_folder_recursive(&child_path)?;
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ impl Default for FilesystemWatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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)
|
self.watcher.watch(path, RecursiveMode::Recursive)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
use crate::entity::Entity;
|
use crate::entity::{EntityIndex, Entity};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use std::{collections::{VecDeque, HashSet}, num::Wrapping, sync::Arc};
|
use std::{collections::{VecDeque, HashSet}, num::Wrapping, sync::Arc};
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
pub struct GuidEntityAllocator {
|
pub struct GuidEntityAllocator {
|
||||||
entities: Arc<RwLock<HashSet<Entity>>>,
|
entities: Arc<RwLock<HashSet<EntityIndex>>>,
|
||||||
next_ids: Arc<RwLock<VecDeque<Entity>>>,
|
next_ids: Arc<RwLock<VecDeque<Entity>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GuidEntityAllocator {
|
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>) {
|
pub fn push_next_ids(&self, ids: impl Iterator<Item = Entity>) {
|
||||||
self.next_ids.write().extend(ids);
|
self.next_ids.write().extend(ids);
|
||||||
@ -23,7 +23,7 @@ impl GuidEntityAllocator {
|
|||||||
Entity::new(rand::random::<u32>(), Wrapping(1))
|
Entity::new(rand::random::<u32>(), Wrapping(1))
|
||||||
};
|
};
|
||||||
|
|
||||||
self.entities.write().insert(entity);
|
self.entities.write().insert(entity.index());
|
||||||
entity
|
entity
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ impl GuidEntityAllocator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn delete_entity(&self, entity: Entity) -> bool {
|
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(); }
|
pub(crate) fn delete_all_entities(&self) { self.entities.write().clear(); }
|
||||||
|
@ -1,19 +1,26 @@
|
|||||||
|
mod loaded_scenes;
|
||||||
mod scene;
|
mod scene;
|
||||||
mod scene_loader;
|
mod scene_spawner;
|
||||||
pub mod serde;
|
pub mod serde;
|
||||||
|
|
||||||
|
pub use loaded_scenes::*;
|
||||||
pub use scene::*;
|
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;
|
use bevy_asset::AddAsset;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ScenePlugin;
|
pub struct ScenePlugin;
|
||||||
|
|
||||||
|
pub const SCENE_STAGE: &str = "scene";
|
||||||
|
|
||||||
impl AppPlugin for ScenePlugin {
|
impl AppPlugin for ScenePlugin {
|
||||||
fn build(&self, app: &mut AppBuilder) {
|
fn build(&self, app: &mut AppBuilder) {
|
||||||
app.add_asset::<Scene>()
|
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 legion::prelude::{Resources, World};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::num::Wrapping;
|
use std::num::Wrapping;
|
||||||
use thiserror::Error;
|
|
||||||
use crate::serde::SceneSerializer;
|
use crate::serde::SceneSerializer;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -17,12 +16,6 @@ pub struct Entity {
|
|||||||
pub components: Vec<DynamicProperties>,
|
pub components: Vec<DynamicProperties>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum SceneAddError {
|
|
||||||
#[error("Scene contains an unregistered component.")]
|
|
||||||
UnregisteredComponent { type_name: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scene {
|
impl Scene {
|
||||||
pub fn from_world(world: &World, component_registry: &ComponentRegistry) -> Self {
|
pub fn from_world(world: &World, component_registry: &ComponentRegistry) -> Self {
|
||||||
let mut scene = Scene::default();
|
let mut scene = Scene::default();
|
||||||
@ -63,33 +56,6 @@ impl Scene {
|
|||||||
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
|
// TODO: move to AssetSaver when it is implemented
|
||||||
pub fn serialize_ron(&self, registry: &PropertyTypeRegistry) -> Result<String, ron::Error> {
|
pub fn serialize_ron(&self, registry: &PropertyTypeRegistry) -> Result<String, ron::Error> {
|
||||||
let pretty_config = ron::ser::PrettyConfig::default()
|
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()
|
.add_default_plugins()
|
||||||
// Registering components informs Bevy that they exist. This allows them to be used when loading scenes
|
// 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.
|
// 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.
|
// 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.
|
// The core Bevy plugins already register their components, so you only need this step for custom components.
|
||||||
.register_component::<ComponentA>()
|
.register_component::<ComponentA>()
|
||||||
.register_component::<ComponentB>()
|
.register_component::<ComponentB>()
|
||||||
.add_startup_system(save_scene_system)
|
.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();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registered components must implement the `Properties` and `FromResources` traits.
|
// Registered components must implement the `Properties` and `FromResources` traits.
|
||||||
// The `Properties` trait enables serialization, deserialization, dynamic property access, and change detection.
|
// 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.
|
// `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.
|
// The `FromResources` trait determines how your component is constructed when it loads. For simple use cases you can just
|
||||||
// For simple use cases you can just implement the `Default` trait (which automatically implements FromResources)
|
// implement the `Default` trait (which automatically implements FromResources). The simplest registered component just needs
|
||||||
// The simplest registered component just needs these two derives:
|
// these two derives:
|
||||||
#[derive(Properties, Default)]
|
#[derive(Properties, Default)]
|
||||||
struct ComponentA {
|
struct ComponentA {
|
||||||
pub x: f32,
|
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
|
// 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.
|
// 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)]
|
#[derive(Properties)]
|
||||||
struct ComponentB {
|
struct ComponentB {
|
||||||
pub value: String,
|
pub value: String,
|
||||||
@ -47,8 +48,59 @@ impl FromResources for ComponentB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_scene_system(world: &mut World, resources: &mut Resources) {
|
fn load_scene_system(asset_server: Res<AssetServer>, mut scene_spawner: ResMut<SceneSpawner>) {
|
||||||
// Scenes can be created from any ECS World.
|
// 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
|
world
|
||||||
.build()
|
.build()
|
||||||
.build_entity()
|
.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.
|
// The component registry resource contains information about all registered components. This is used to construct scenes.
|
||||||
let type_registry = resources.get::<TypeRegistry>().unwrap();
|
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:
|
// Scenes can be serialized like this:
|
||||||
println!(
|
println!(
|
||||||
@ -74,20 +126,3 @@ fn save_scene_system(world: &mut World, resources: &mut Resources) {
|
|||||||
|
|
||||||
// TODO: save scene
|
// 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,
|
ActiveCamera, ActiveCamera2d, Camera, CameraType, Color, ColorSource, Renderable,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "scene")]
|
#[cfg(feature = "scene")]
|
||||||
pub use crate::scene::Scene;
|
pub use crate::scene::{Scene, SceneSpawner};
|
||||||
#[cfg(feature = "text")]
|
#[cfg(feature = "text")]
|
||||||
pub use crate::text::Font;
|
pub use crate::text::Font;
|
||||||
#[cfg(feature = "transform")]
|
#[cfg(feature = "transform")]
|
||||||
|
Loading…
Reference in New Issue
Block a user