use std::fs;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use crate::camera::Camera;
use crate::effect_pass::EffectPass;
use crate::gpu::GpuContext;
use crate::post_process::{PostProcessPass, WorldPostProcessPass};
pub struct HotShader {
path: PathBuf,
last_modified: SystemTime,
source: String,
}
impl HotShader {
pub fn new(path: impl AsRef<Path>) -> std::io::Result<Self> {
let path = path.as_ref().to_path_buf();
let source = fs::read_to_string(&path)?;
let last_modified = fs::metadata(&path)?.modified()?;
Ok(Self {
path,
last_modified,
source,
})
}
pub fn check_reload(&mut self) -> bool {
let Ok(metadata) = fs::metadata(&self.path) else {
return false;
};
let Ok(modified) = metadata.modified() else {
return false;
};
if modified > self.last_modified {
if let Ok(source) = fs::read_to_string(&self.path) {
self.source = source;
self.last_modified = modified;
return true;
}
}
false
}
pub fn source(&self) -> &str {
&self.source
}
pub fn path(&self) -> &Path {
&self.path
}
}
pub struct HotEffectPass {
shader: HotShader,
pass: Option<EffectPass>,
uses_camera: bool,
}
impl HotEffectPass {
pub fn new(gpu: &GpuContext, path: impl AsRef<Path>) -> std::io::Result<Self> {
let shader = HotShader::new(path)?;
let pass = Self::try_compile(gpu, shader.source(), false);
Ok(Self {
shader,
pass,
uses_camera: false,
})
}
pub fn new_world(gpu: &GpuContext, path: impl AsRef<Path>) -> std::io::Result<Self> {
let shader = HotShader::new(path)?;
let pass = Self::try_compile(gpu, shader.source(), true);
Ok(Self {
shader,
pass,
uses_camera: true,
})
}
fn try_compile(gpu: &GpuContext, source: &str, uses_camera: bool) -> Option<EffectPass> {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
if uses_camera {
EffectPass::new_world(gpu, source)
} else {
EffectPass::new(gpu, source)
}
}));
match result {
Ok(pass) => Some(pass),
Err(_) => {
eprintln!("[hot-reload] Shader compilation panicked, keeping previous version");
None
}
}
}
pub fn check_reload(&mut self, gpu: &GpuContext) {
if self.shader.check_reload() {
eprintln!("[hot-reload] Reloading shader: {:?}", self.shader.path());
if let Some(new_pass) = Self::try_compile(gpu, self.shader.source(), self.uses_camera) {
self.pass = Some(new_pass);
eprintln!("[hot-reload] Shader compiled successfully");
} else {
eprintln!("[hot-reload] Shader compilation failed, keeping previous version");
}
}
}
pub fn render(&self, gpu: &GpuContext, render_pass: &mut wgpu::RenderPass, time: f32) {
if let Some(ref pass) = self.pass {
pass.render(gpu, render_pass, time);
}
}
pub fn render_with_camera(
&self,
gpu: &GpuContext,
render_pass: &mut wgpu::RenderPass,
time: f32,
camera: &Camera,
) {
if let Some(ref pass) = self.pass {
pass.render_with_camera(gpu, render_pass, time, camera);
}
}
pub fn uses_camera(&self) -> bool {
self.uses_camera
}
pub fn is_valid(&self) -> bool {
self.pass.is_some()
}
}
pub struct HotPostProcessPass {
shader: HotShader,
pass: Option<PostProcessPass>,
}
impl HotPostProcessPass {
pub fn new(gpu: &GpuContext, path: impl AsRef<Path>) -> std::io::Result<Self> {
let shader = HotShader::new(path)?;
let pass = Self::try_compile(gpu, shader.source());
Ok(Self { shader, pass })
}
fn try_compile(gpu: &GpuContext, source: &str) -> Option<PostProcessPass> {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
PostProcessPass::new(gpu, source)
}));
match result {
Ok(pass) => Some(pass),
Err(_) => {
eprintln!("[hot-reload] Shader compilation panicked, keeping previous version");
None
}
}
}
pub fn check_reload(&mut self, gpu: &GpuContext) {
if self.shader.check_reload() {
eprintln!("[hot-reload] Reloading shader: {:?}", self.shader.path());
if let Some(new_pass) = Self::try_compile(gpu, self.shader.source()) {
self.pass = Some(new_pass);
eprintln!("[hot-reload] Shader compiled successfully");
} else {
eprintln!("[hot-reload] Shader compilation failed, keeping previous version");
}
}
}
pub fn render(
&self,
gpu: &GpuContext,
render_pass: &mut wgpu::RenderPass,
time: f32,
input_view: &wgpu::TextureView,
) {
if let Some(ref pass) = self.pass {
pass.render(gpu, render_pass, time, input_view);
}
}
pub fn is_valid(&self) -> bool {
self.pass.is_some()
}
}
pub struct HotWorldPostProcessPass {
shader: HotShader,
pass: Option<WorldPostProcessPass>,
}
impl HotWorldPostProcessPass {
pub fn new(gpu: &GpuContext, path: impl AsRef<Path>) -> std::io::Result<Self> {
let shader = HotShader::new(path)?;
let pass = Self::try_compile(gpu, shader.source());
Ok(Self { shader, pass })
}
fn try_compile(gpu: &GpuContext, source: &str) -> Option<WorldPostProcessPass> {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
WorldPostProcessPass::new(gpu, source)
}));
match result {
Ok(pass) => Some(pass),
Err(_) => {
eprintln!("[hot-reload] Shader compilation panicked, keeping previous version");
None
}
}
}
pub fn check_reload(&mut self, gpu: &GpuContext) {
if self.shader.check_reload() {
eprintln!("[hot-reload] Reloading shader: {:?}", self.shader.path());
if let Some(new_pass) = Self::try_compile(gpu, self.shader.source()) {
self.pass = Some(new_pass);
eprintln!("[hot-reload] Shader compiled successfully");
} else {
eprintln!("[hot-reload] Shader compilation failed, keeping previous version");
}
}
}
pub fn render(
&self,
gpu: &GpuContext,
render_pass: &mut wgpu::RenderPass,
time: f32,
camera: &Camera,
input_view: &wgpu::TextureView,
) {
if let Some(ref pass) = self.pass {
pass.render(gpu, render_pass, time, camera, input_view);
}
}
pub fn is_valid(&self) -> bool {
self.pass.is_some()
}
}