scene: hot scene reloading. update load_scene example

This commit is contained in:
Carter Anderson 2020-05-29 12:56:32 -07:00
parent a7d9f8d0ff
commit 065a94aca8
10 changed files with 273 additions and 139 deletions

View file

@ -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",
},
}, ],
),]
},
],
),
]

View file

@ -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)?;
}

View file

@ -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)
}
}

View file

@ -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(); }

View file

@ -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);
}
}

View file

@ -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()

View 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);
}

View file

@ -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();
}

View file

@ -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")]