Skip to content

Commit

Permalink
Fix hot reloading crashes
Browse files Browse the repository at this point in the history
  • Loading branch information
branpk committed Sep 15, 2023
1 parent e429ddf commit a8888bb
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 38 deletions.
3 changes: 3 additions & 0 deletions wafel_app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ egui-winit = "0.22.0"
sysinfo = { version = "0.29.10", default-features = false }
wafel_viz = { path = "../wafel_viz", features = ["wgpu"] }
hot-lib-reloader = "0.6.5"

[features]
reload = []
102 changes: 77 additions & 25 deletions wafel_app/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
use std::fmt;
use std::{
fmt,
sync::{Arc, Mutex},
};

use wafel_app_ui::Wafel;
use wafel_viz::{VizRenderData, VizRenderer};
use winit::{event::WindowEvent, window::Window};

use crate::{egui_state::EguiState, env::WafelEnv, hot_reload, window::WindowedApp};

#[allow(unused)]
pub struct WafelApp {
env: WafelEnv,
egui_state: EguiState,
egui_state: Arc<Mutex<Option<EguiState>>>,
is_reloading: Arc<Mutex<bool>>,
viz_renderer: VizRenderer,
viz_render_data: Vec<VizRenderData>,
wafel: Wafel,
output_format: wgpu::TextureFormat,
msaa_samples: u32,
}

Expand All @@ -23,27 +29,69 @@ impl WindowedApp for WafelApp {
output_format: wgpu::TextureFormat,
msaa_samples: u32,
) -> Self {
let egui_state = Arc::new(Mutex::new(Some(EguiState::new(
window,
device,
output_format,
msaa_samples,
))));

// To avoid crashes when hot reloading, we need to drop EguiState before the reload happens,
// and recreate it afterward.
let is_reloading = Arc::new(Mutex::new(false));
#[cfg(feature = "reload")]
{
let egui_state = Arc::clone(&egui_state);
let is_reloading = Arc::clone(&is_reloading);

let observer = hot_reload::subscribe();
std::thread::spawn(move || loop {
observer.wait_for_about_to_reload();
*is_reloading.lock().unwrap() = true;
*egui_state.lock().unwrap() = None;

observer.wait_for_reload();
*is_reloading.lock().unwrap() = false;
});
}

WafelApp {
env,
egui_state: EguiState::new(window, device, output_format, msaa_samples),
egui_state,
is_reloading,
viz_renderer: VizRenderer::new(device, output_format, msaa_samples),
viz_render_data: Vec::new(),
wafel: Wafel::default(),
output_format,
msaa_samples,
}
}

fn window_event(&mut self, event: &WindowEvent<'_>) {
let consumed = self.egui_state.window_event(event);
if !consumed {
// handle event
if let Some(egui_state) = self.egui_state.lock().unwrap().as_mut() {
let consumed = egui_state.window_event(event);
if !consumed {
// handle event
}
}
}

fn update(&mut self, window: &Window) {
self.egui_state.run(window, |ctx| {
self.viz_render_data = hot_reload::wafel_show(&mut self.wafel, &self.env, ctx);
});
fn update(&mut self, window: &Window, _device: &wgpu::Device) {
// Recreate EguiState if necessary after hot reloading.
#[cfg(feature = "reload")]
if !*self.is_reloading.lock().unwrap() {
let mut egui_state = self.egui_state.lock().unwrap();
egui_state.get_or_insert_with(|| {
EguiState::new(window, _device, self.output_format, self.msaa_samples)
});
}

if let Some(egui_state) = self.egui_state.lock().unwrap().as_mut() {
egui_state.run(window, |ctx| {
ctx.input(|input| input.key_down(egui::Key::A));
self.viz_render_data = hot_reload::wafel_show(&mut self.wafel, &self.env, ctx);
});
}
}

fn render(
Expand All @@ -55,6 +103,9 @@ impl WindowedApp for WafelApp {
output_size: [u32; 2],
scale_factor: f32,
) {
self.viz_render_data
.insert(0, VizRenderData::new([0, 0], output_size));

let msaa_output_texture = device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
Expand Down Expand Up @@ -136,23 +187,24 @@ impl WafelApp {
scale_factor: f32,
clear_op: &mut Option<wgpu::LoadOp<wgpu::Color>>,
) {
self.egui_state
.prepare(device, queue, encoder, output_size, scale_factor);
if let Some(egui_state) = self.egui_state.lock().unwrap().as_mut() {
egui_state.prepare(device, queue, encoder, output_size, scale_factor);

let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: msaa_output_view,
resolve_target: Some(output_view),
ops: wgpu::Operations {
load: clear_op.take().unwrap_or(wgpu::LoadOp::Load),
store: true,
},
})],
depth_stencil_attachment: None,
});
let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: msaa_output_view,
resolve_target: Some(output_view),
ops: wgpu::Operations {
load: clear_op.take().unwrap_or(wgpu::LoadOp::Load),
store: true,
},
})],
depth_stencil_attachment: None,
});

self.egui_state.render(&mut rp);
egui_state.render(&mut rp);
}
}

fn render_viz(
Expand Down
15 changes: 11 additions & 4 deletions wafel_app/src/hot_reload.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
#[cfg(debug_assertions)]
#[cfg(feature = "reload")]
pub use hot_wafel_app_ui::*;
#[cfg(not(debug_assertions))]
#[cfg(not(feature = "reload"))]
pub use wafel_app_ui::*;

#[cfg(debug_assertions)]
#[hot_lib_reloader::hot_module(dylib = "wafel_app_ui", lib_dir = "target/debug")]
#[cfg(feature = "reload")]
#[hot_lib_reloader::hot_module(
dylib = "wafel_app_ui",
lib_dir = "target/debug",
file_watch_debounce = 500
)]
mod hot_wafel_app_ui {
pub use wafel_app_ui::{Env, Wafel};
pub use wafel_viz::VizRenderData;

hot_functions_from_file!("wafel_app_ui/src/lib.rs");

#[lib_change_subscription]
pub fn subscribe() -> hot_lib_reloader::LibReloadObserver {}
}
11 changes: 8 additions & 3 deletions wafel_app/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
//! The executable for the main Wafel GUI.
//!
//! It is possible to hot reload the [wafel_app_ui] crate while this binary is
//! running in debug mode by building it. You can automatically rebuild on file
//! edit using this command:
//! running if the `reload` feature is enabled.
//! Commands to run (in separate terminals):
//!
//! ```sh
//! ```sh'
//! cargo run -p wafel_app --features reload
//! cargo watch -w wafel_app_ui -x 'build -p wafel_app_ui'
//! ```

Expand Down Expand Up @@ -36,6 +37,10 @@ fn main() {
std::env::consts::ARCH
);

if cfg!(feature = "reload") {
tracing::info!("Hot reload enabled");
}

let title = format!("Wafel {}", env.wafel_version());
window::run_app::<app::WafelApp>(env, &title);
}
4 changes: 2 additions & 2 deletions wafel_app/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub trait WindowedApp: Sized + 'static {

fn window_event(&mut self, event: &WindowEvent<'_>);

fn update(&mut self, window: &Window);
fn update(&mut self, window: &Window, device: &wgpu::Device);

fn render(
&mut self,
Expand Down Expand Up @@ -166,7 +166,7 @@ pub fn run_app<A: WindowedApp>(env: WafelEnv, title: &str) {
}
Event::MainEventsCleared => {
if !first_render {
app.update(&window);
app.update(&window, &device);
}

if surface_config.width != 0 && surface_config.height != 0 {
Expand Down
9 changes: 5 additions & 4 deletions wafel_app_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
//! These operations are done indirectly through the [Env] trait, which can be
//! overriden as needed.
//!
//! It is possible to hot reload this crate while `wafel_app` is running.
//! Simply rebuild the crate while the app is running in debug mode. You can
//! automatically rebuild on file edit using this command:
//! It is possible to hot reload this crate while `wafel_app` is running by
//! rebuilding it while the app is running with the `reload` feature enabled.
//! Commands to run (in separate terminals):
//!
//! ```sh
//! ```sh'
//! cargo run -p wafel_app --features reload
//! cargo watch -w wafel_app_ui -x 'build -p wafel_app_ui'
//! ```

Expand Down

0 comments on commit a8888bb

Please sign in to comment.