Skip to content

Commit

Permalink
Move hot reloading into a separate module
Browse files Browse the repository at this point in the history
  • Loading branch information
CatThingy committed Aug 9, 2022
1 parent 6305ce6 commit b290cd3
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 111 deletions.
44 changes: 0 additions & 44 deletions src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,8 @@ use bevy::ecs::system::EntityCommands;
use bevy::prelude::{FromWorld, Handle};
use bevy::reflect::Uuid;
use bevy::utils::HashMap;
#[cfg(feature = "hot_reloading")]
use crossbeam_channel::Receiver;
use dyn_clone::DynClone;
use indexmap::IndexSet;
#[cfg(feature = "hot_reloading")]
use notify::{Event, RecommendedWatcher, RecursiveMode, Result, Watcher};
use serde::{Deserialize, Serialize};

use crate::plugin::DefaultProtoDeserializer;
Expand All @@ -40,34 +36,6 @@ impl From<&HandlePath> for HandleId {

type UuidHandleMap = HashMap<Uuid, HandleUntyped>;

// Copied from bevy_asset's implementation
// https://github.com/bevyengine/bevy/blob/main/crates/bevy_asset/src/filesystem_watcher.rs
#[cfg(feature = "hot_reloading")]
pub(crate) struct FilesystemWatcher {
pub(crate) watcher: RecommendedWatcher,
pub(crate) receiver: Receiver<Result<Event>>,
}

#[cfg(feature = "hot_reloading")]
impl FilesystemWatcher {
/// Watch for changes recursively at the provided path.
pub fn watch<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<()> {
self.watcher.watch(path.as_ref(), RecursiveMode::Recursive)
}
}

#[cfg(feature = "hot_reloading")]
impl Default for FilesystemWatcher {
fn default() -> Self {
let (sender, receiver) = crossbeam_channel::unbounded();
let watcher: RecommendedWatcher = RecommendedWatcher::new(move |res| {
sender.send(res).expect("Watch event send failure.");
})
.expect("Failed to create filesystem watcher.");
FilesystemWatcher { watcher, receiver }
}
}

/// A resource containing data for all prototypes that need data stored
pub struct ProtoData {
/// Maps Prototype Name -> Component Type -> HandleId -> Asset Type -> HandleUntyped
Expand All @@ -79,9 +47,6 @@ pub struct ProtoData {
>,
>,
pub(crate) prototypes: HashMap<String, Box<dyn Prototypical>>,

#[cfg(feature = "hot_reloading")]
pub(crate) watcher: FilesystemWatcher,
}

impl ProtoData {
Expand All @@ -90,8 +55,6 @@ impl ProtoData {
Self {
handles: HashMap::default(),
prototypes: HashMap::default(),
#[cfg(feature = "hot_reloading")]
watcher: FilesystemWatcher::default(),
}
}

Expand Down Expand Up @@ -261,8 +224,6 @@ impl FromWorld for ProtoData {
let mut myself = Self {
handles: Default::default(),
prototypes: HashMap::default(),
#[cfg(feature = "hot_reloading")]
watcher: FilesystemWatcher::default(),
};

let options = world
Expand Down Expand Up @@ -536,9 +497,6 @@ pub struct ProtoDataOptions {
/// };
/// ```
pub extensions: Option<Vec<&'static str>>,
/// Whether to enable hot-reloading or not
#[cfg(feature = "hot_reloading")]
pub hot_reload: bool,
}

impl Default for ProtoDataOptions {
Expand All @@ -548,8 +506,6 @@ impl Default for ProtoDataOptions {
recursive_loading: Default::default(),
deserializer: Box::new(DefaultProtoDeserializer),
extensions: Default::default(),
#[cfg(feature = "hot_reloading")]
hot_reload: false,
}
}
}
81 changes: 81 additions & 0 deletions src/hot_reload.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use bevy::prelude::{App, Plugin, Res, ResMut};
use crossbeam_channel::Receiver;
use notify::{Event, RecommendedWatcher, RecursiveMode, Result, Watcher};

use crate::prelude::{ProtoData, ProtoDataOptions};

// Copied from bevy_asset's implementation
// https://github.com/bevyengine/bevy/blob/main/crates/bevy_asset/src/filesystem_watcher.rs
struct FilesystemWatcher {
watcher: RecommendedWatcher,
receiver: Receiver<Result<Event>>,
}

impl FilesystemWatcher {
/// Watch for changes recursively at the provided path.
fn watch<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<()> {
self.watcher.watch(path.as_ref(), RecursiveMode::Recursive)
}
}

impl Default for FilesystemWatcher {
fn default() -> Self {
let (sender, receiver) = crossbeam_channel::unbounded();
let watcher: RecommendedWatcher = RecommendedWatcher::new(move |res| {
sender.send(res).expect("Watch event send failure.");
})
.expect("Failed to create filesystem watcher.");
FilesystemWatcher { watcher, receiver }
}
}

// Copied from bevy_asset's filesystem watching implementation:
// https://github.com/bevyengine/bevy/blob/main/crates/bevy_asset/src/io/file_asset_io.rs#L167-L199
fn watch_for_changes(
watcher: Res<FilesystemWatcher>,
mut proto_data: ResMut<ProtoData>,
options: Res<ProtoDataOptions>,
) {
let mut changed = bevy::utils::HashSet::default();
loop {
let event = match watcher.receiver.try_recv() {
Ok(result) => result.unwrap(),
Err(crossbeam_channel::TryRecvError::Empty) => break,
Err(crossbeam_channel::TryRecvError::Disconnected) => {
panic!("FilesystemWatcher disconnected.")
}
};
if let notify::event::Event {
kind: notify::event::EventKind::Modify(_),
paths,
..
} = event
{
for path in &paths {
if !changed.contains(path) {
if let Ok(data) = std::fs::read_to_string(path) {
if let Some(proto) = options.deserializer.deserialize(&data) {
proto_data
.prototypes
.insert(proto.name().to_string(), proto);
}
}
}
}
changed.extend(paths);
}
}
}

pub(crate) struct HotReloadPlugin {
pub(crate) path: String,
}

impl Plugin for HotReloadPlugin {
fn build(&self, app: &mut App) {
let mut watcher = FilesystemWatcher::default();
watcher.watch(self.path.clone()).unwrap();

app.insert_resource(watcher).add_system(watch_for_changes);
}
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ pub use plugin::ProtoPlugin;
mod prototype;
pub use prototype::{deserialize_templates_list, Prototype, Prototypical};

#[cfg(feature = "hot_reloading")]
mod hot_reload;

pub mod data;
#[macro_use]
mod utils;
Expand Down
78 changes: 11 additions & 67 deletions src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ impl ProtoPlugin {
recursive_loading: false,
deserializer: Box::new(DefaultProtoDeserializer),
extensions: Some(vec!["yaml", "json"]),
#[cfg(feature = "hot_reloading")]
hot_reload: false,
}),
}
}
Expand All @@ -62,8 +60,6 @@ impl ProtoPlugin {
recursive_loading: true,
deserializer: Box::new(DefaultProtoDeserializer),
extensions: Some(vec!["yaml", "json"]),
#[cfg(feature = "hot_reloading")]
hot_reload: false,
}),
}
}
Expand Down Expand Up @@ -91,8 +87,6 @@ impl ProtoPlugin {
recursive_loading: false,
deserializer: Box::new(DefaultProtoDeserializer),
extensions: Some(vec!["yaml", "json"]),
#[cfg(feature = "hot_reloading")]
hot_reload: false,
}),
}
}
Expand Down Expand Up @@ -120,84 +114,34 @@ impl ProtoPlugin {
recursive_loading: true,
deserializer: Box::new(DefaultProtoDeserializer),
extensions: Some(vec!["yaml", "json"]),
#[cfg(feature = "hot_reloading")]
hot_reload: false,
}),
}
}
}

impl Plugin for ProtoPlugin {
fn build(&self, app: &mut App) {
if let Some(opts) = &self.options {
// Insert custom prototype options
app.insert_resource(opts.clone());
#[cfg(feature = "hot_reloading")]
if opts.hot_reload {
app.add_startup_system(begin_watch);
app.add_system(watch_for_changes);
}
} else {
// Insert default options
app.insert_resource(ProtoDataOptions {
let opts = self.options.clone();
let opts = opts.unwrap_or(
ProtoDataOptions {
directories: vec![String::from("assets/prototypes")],
recursive_loading: false,
deserializer: Box::new(DefaultProtoDeserializer),
extensions: Some(vec!["yaml", "json"]),
#[cfg(feature = "hot_reloading")]
hot_reload: false,
});
}
}
);

#[cfg(feature = "hot_reloading")]
app.add_plugin(crate::hot_reload::HotReloadPlugin {
path: opts.directories[0].clone()
});

app.insert_resource(opts);
// Initialize prototypes
app.init_resource::<ProtoData>();
}
}

fn begin_watch(
mut data: bevy::prelude::ResMut<ProtoData>,
opts: bevy::prelude::Res<ProtoDataOptions>,
) {
data.watcher.watch(opts.directories[0].clone()).unwrap();
}

// Copied from bevy_asset's filesystem watching implementation:
// https://github.com/bevyengine/bevy/blob/main/crates/bevy_asset/src/io/file_asset_io.rs#L167-L199
fn watch_for_changes(
mut proto_data: bevy::prelude::ResMut<ProtoData>,
options: bevy::prelude::Res<ProtoDataOptions>,
) {
let mut changed = bevy::utils::HashSet::default();
loop {
let event = match proto_data.watcher.receiver.try_recv() {
Ok(result) => result.unwrap(),
Err(crossbeam_channel::TryRecvError::Empty) => break,
Err(crossbeam_channel::TryRecvError::Disconnected) => {
panic!("FilesystemWatcher disconnected.")
}
};
if let notify::event::Event {
kind: notify::event::EventKind::Modify(_),
paths,
..
} = event
{
for path in &paths {
if !changed.contains(path) {
if let Ok(data) = std::fs::read_to_string(path) {
if let Some(proto) = options.deserializer.deserialize(&data) {
proto_data
.prototypes
.insert(proto.name().to_string(), proto);
}
}
}
}
changed.extend(paths);
}
}
}

#[derive(Clone)]
pub(crate) struct DefaultProtoDeserializer;

Expand Down

0 comments on commit b290cd3

Please sign in to comment.