Skip to content

Commit

Permalink
Add ProtoAssetEvent
Browse files Browse the repository at this point in the history
  • Loading branch information
MrGVSV committed Apr 29, 2023
1 parent 1e6e439 commit 73b1414
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 43 deletions.
3 changes: 2 additions & 1 deletion bevy_proto_backend/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use parking_lot::Mutex;

use crate::impls;
use crate::load::ProtoLoader;
use crate::proto::{ProtoAsset, ProtoStorage, Prototypical};
use crate::proto::{ProtoAsset, ProtoAssetEvent, ProtoStorage, Prototypical};
use crate::registration::{on_proto_asset_event, ProtoRegistry};
use crate::tree::{AccessOp, ChildAccess, EntityAccess, ProtoEntity};

Expand Down Expand Up @@ -51,6 +51,7 @@ impl<T: Prototypical> Plugin for ProtoBackendPlugin<T> {
.init_resource::<ProtoStorage<T>>()
.init_asset_loader::<ProtoLoader<T>>()
.add_asset::<T>()
.add_event::<ProtoAssetEvent<T>>()
.add_system(on_proto_asset_event::<T>);

impls::register_impls(app);
Expand Down
39 changes: 39 additions & 0 deletions bevy_proto_backend/src/proto/event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use crate::proto::Prototypical;
use bevy::asset::Handle;

/// Asset lifecycle events for [prototype] assets.
///
/// This is analogous to [`AssetEvent`], but accounts for prototype
/// caching and registration.
/// This event should be preferred over using the `AssetEvent` directly.
///
/// [prototype]: Prototypical
/// [`AssetEvent`]: bevy::asset::AssetEvent
pub enum ProtoAssetEvent<T: Prototypical> {
/// This event is fired when a prototype has been successfully created,
/// registered, and cached.
Created {
/// The ID of the created prototype.
id: T::Id,
/// A weak handle to the prototype asset.
handle: Handle<T>,
},
/// This event is fired when a prototype has been modified, such as when
/// a change is made to the file while hot-reloading is enabled.
Modified {
/// The ID of the modified prototype.
id: T::Id,
/// A weak handle to the prototype asset.
handle: Handle<T>,
},
/// This event is fired when a prototype has been fully unloaded.
Removed {
/// The ID of the removed prototype.
///
/// This will almost always be `Some` unless a duplicate removal happens
/// to occur at the same time, in which case it may be `None`.
id: Option<T::Id>,
/// A weak handle to the prototype asset.
handle: Handle<T>,
},
}
2 changes: 2 additions & 0 deletions bevy_proto_backend/src/proto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub use commands::*;
pub use component::*;
pub use config::*;
pub use error::*;
pub use event::*;
pub use prototypes::*;
pub use prototypical::*;
pub(crate) use storage::*;
Expand All @@ -14,6 +15,7 @@ mod commands;
mod component;
mod config;
mod error;
mod event;
mod prototypes;
mod prototypical;
mod storage;
2 changes: 1 addition & 1 deletion bevy_proto_backend/src/registration/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl<'w, T: Prototypical> ProtoManager<'w, T> {
.register(handle, &self.prototypes, &mut self.config)
}

pub fn unregister<H: Into<HandleId>>(&mut self, handle: H) -> bool {
pub fn unregister<H: Into<HandleId>>(&mut self, handle: H) -> Option<T::Id> {
self.registry
.unregister(handle, &self.prototypes, &mut self.config)
}
Expand Down
8 changes: 4 additions & 4 deletions bevy_proto_backend/src/registration/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ impl<T: Prototypical> ProtoRegistry<T> {
handle: H,
prototypes: &'w Assets<T>,
config: &'w mut T::Config,
) -> bool {
) -> Option<T::Id> {
let handle_id = handle.into();

if let Some(id) = self.ids.remove(&handle_id) {
Expand All @@ -67,9 +67,9 @@ impl<T: Prototypical> ProtoRegistry<T> {
self.register(dependent, prototypes, config).ok();
}
}
true
Some(id)
} else {
false
None
}
}

Expand Down Expand Up @@ -144,7 +144,7 @@ impl<T: Prototypical> ProtoRegistry<T> {
}

// If already registered -> unregister so we can update all the cached data
if self.unregister(&handle, prototypes, config) {
if self.unregister(&handle, prototypes, config).is_some() {
config.on_unregister_prototype(prototype, handle.clone());
}

Expand Down
36 changes: 21 additions & 15 deletions bevy_proto_backend/src/registration/systems.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
use bevy::asset::AssetEvent;
use bevy::prelude::{error, EventReader};
use bevy::prelude::{error, EventReader, EventWriter};

use crate::proto::Prototypical;
use crate::proto::{ProtoAssetEvent, Prototypical};
use crate::registration::ProtoManager;

/// Handles the registration of loaded, modified, and removed prototypes.
pub(crate) fn on_proto_asset_event<T: Prototypical>(
mut events: EventReader<AssetEvent<T>>,
mut manager: ProtoManager<T>,
mut proto_events: EventWriter<ProtoAssetEvent<T>>,
) {
for event in events.iter() {
match event {
AssetEvent::Created { handle } => {
if let Err(err) = manager.register(handle) {
error!("could not register prototype: {}", err);
}
}
AssetEvent::Modified { handle } => {
if let Err(err) = manager.register(handle) {
error!("could not re-register modified prototype: {}", err);
}
}
AssetEvent::Removed { handle } => {
manager.unregister(handle);
}
AssetEvent::Created { handle } => match manager.register(handle) {
Ok(proto) => proto_events.send(ProtoAssetEvent::Created {
id: proto.id().clone(),
handle: handle.clone_weak(),
}),
Err(err) => error!("could not register prototype: {}", err),
},
AssetEvent::Modified { handle } => match manager.register(handle) {
Ok(proto) => proto_events.send(ProtoAssetEvent::Modified {
id: proto.id().clone(),
handle: handle.clone_weak(),
}),
Err(err) => error!("could not re-register modified prototype: {}", err),
},
AssetEvent::Removed { handle } => proto_events.send(ProtoAssetEvent::Removed {
id: manager.unregister(handle),
handle: handle.clone_weak(),
}),
}
}
}
48 changes: 26 additions & 22 deletions examples/hot_reload.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
//! This example demonstrates the hot-reloading capabilities of prototypes.
//!
//! The implementation details of this example aren't important
//! (except for enabling [`AssetPlugin::watch_for_changes`]).
//! It's pretty easy to enable hot-reloading in Bevy— just enable [`AssetPlugin::watch_for_changes`].
//! This allows changes to any prototype to be automatically picked up.
//!
//! This example also uses the [`ProtoAssetEvent`] event to reload an existing entity if
//! its prototype changes, which can allow for faster development.
//!
//! Please note that hot-reloading is far from perfect.
//! Changing the IDs of a prototype or its hierarchical structure may cause the
//! reload to fail upon prototype re-registration.
//! However, it can still be a great tool to use for fast prototyping.
//!
//! Just run the example and try it out for yourself!
use bevy::prelude::*;
Expand All @@ -17,12 +26,7 @@ fn main() {
}))
.add_plugin(ProtoPlugin::default())
.add_startup_systems((setup, load))
.add_systems((
spawn.run_if(
prototype_ready("ReloadableSprite").and_then(resource_changed::<Input<KeyCode>>()),
),
inspect,
))
.add_systems((spawn.run_if(prototype_ready("ReloadableSprite")), inspect))
.run();
}

Expand All @@ -34,15 +38,23 @@ fn spawn(
mut commands: ProtoCommands,
keyboard_input: Res<Input<KeyCode>>,
mut previous: Local<Option<Entity>>,
mut proto_asset_events: EventReader<ProtoAssetEvent>,
) {
if previous.is_none() || keyboard_input.just_pressed(KeyCode::Space) {
*previous = Some(commands.spawn("ReloadableSprite").id());
}

if keyboard_input.just_pressed(KeyCode::Z) {
commands
.entity(previous.unwrap())
.insert("ReloadableSprite");
// Listen for changes:
for proto_asset_event in proto_asset_events.iter() {
match proto_asset_event {
// Only trigger a re-insert of the prototype when modified and if IDs match
ProtoAssetEvent::Modified { id, .. } if id == "ReloadableSprite" => {
commands
.entity(previous.unwrap())
.insert("ReloadableSprite");
}
_ => {}
}
}
}

Expand All @@ -69,23 +81,15 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
parent.spawn(
TextBundle::from_sections([
TextSection::new(
"Press <Space> to spawn a sprite prototype\n",
"Modify the prototype file to see changes\n",
TextStyle {
font: asset_server.load("fonts/JetBrainsMono-Regular.ttf"),
font_size: 32.0,
color: Color::WHITE,
},
),
TextSection::new(
"Or press <Z> to reload the previous one\n",
TextStyle {
font: asset_server.load("fonts/JetBrainsMono-Regular.ttf"),
font_size: 28.0,
color: Color::WHITE,
},
),
TextSection::new(
"(try changing the prototype file between presses)",
"(press <Space> to spawn a new prototype)",
TextStyle {
font: asset_server.load("fonts/JetBrainsMono-Regular.ttf"),
font_size: 16.0,
Expand Down
10 changes: 10 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ pub mod prelude {
/// [prototypes]: Prototype
pub type ProtoEntityCommands<'w, 's, 'a> =
bevy_proto_backend::proto::ProtoEntityCommands<'w, 's, 'a, Prototype>;

/// Asset lifecycle events for [prototype] assets.
///
/// This is analogous to [`AssetEvent`], but accounts for prototype
/// caching and registration.
/// This event should be preferred over using the `AssetEvent` directly.
///
/// [prototype]: Prototype
/// [`AssetEvent`]: bevy::asset::AssetEvent
pub type ProtoAssetEvent = bevy_proto_backend::proto::ProtoAssetEvent<Prototype>;
}

/// Provides access to the [backend crate] that `bevy_proto` is built on.
Expand Down

0 comments on commit 73b1414

Please sign in to comment.