1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use std::error::Error;
use std::fs::File;
use std::io::{self, Read};
use std::iter::Iterator;
use std::path::PathBuf;
use std::sync::mpsc::{self, Receiver};
use std::time::{Duration, Instant};

use gfx;
use notify::{self, DebouncedEvent, RecommendedWatcher, Watcher, RecursiveMode};

use super::*;

pub struct ShaderDirectoryWatcher {
    directory: PathBuf,
    _watcher: RecommendedWatcher,
    watcher_rx: Receiver<DebouncedEvent>,

    last_modification: Instant,
}
impl ShaderDirectoryWatcher {
    pub fn new<P>(directory: P) -> Result<Self, notify::Error>
    where
        PathBuf: From<P>,
    {
        let directory = PathBuf::from(directory);
        let (tx, watcher_rx) = mpsc::channel();
        let mut watcher = notify::watcher(tx, Duration::from_millis(50))?;
        watcher.watch(&directory, RecursiveMode::Recursive)?;

        Ok(Self {
            directory,
            _watcher: watcher,
            watcher_rx,
            last_modification: Instant::now(),
        })
    }

    fn detect_changes(&mut self) {
        while let Ok(event) = self.watcher_rx.try_recv() {
            if let DebouncedEvent::Write(_) = event {
                self.last_modification = Instant::now();
            }
        }
    }
}

fn concat_file_contents<'a, I: Iterator<Item = &'a PathBuf>>(filenames: I) -> io::Result<String> {
    let mut contents = String::new();
    for filename in filenames {
        File::open(filename)?.read_to_string(&mut contents)?;
    }
    Ok(contents)
}

pub struct Shader<R: gfx::Resources> {
    shader_set: gfx::ShaderSet<R>,
    vertex_filenames: Vec<PathBuf>,
    pixel_filenames: Vec<PathBuf>,
    last_update: Instant,
}
impl<R: gfx::Resources> Shader<R> {
    pub fn simple<F: gfx::Factory<R>>(
        factory: &mut F,
        watcher: &mut ShaderDirectoryWatcher,
        vertex_source: ShaderSource,
        pixel_source: ShaderSource,
    ) -> Result<Self, Box<Error>> {
        let vertex_filenames = vertex_source
            .filenames
            .unwrap()
            .into_iter()
            .map(|f| watcher.directory.join(f))
            .collect();
        let pixel_filenames = pixel_source
            .filenames
            .unwrap()
            .into_iter()
            .map(|f| watcher.directory.join(f))
            .collect();

        Ok(Self {
            shader_set: Self::load(factory, &vertex_filenames, &pixel_filenames)?,
            last_update: Instant::now(),
            vertex_filenames,
            pixel_filenames,
        })
    }

    pub fn as_shader_set(&self) -> &gfx::ShaderSet<R> {
        &self.shader_set
    }

    /// Refreshes the shader if necessary. Returns whether a refresh happened.
    pub fn refresh<F: gfx::Factory<R>>(
        &mut self,
        factory: &mut F,
        directory_watcher: &mut ShaderDirectoryWatcher,
    ) -> bool {
        directory_watcher.detect_changes();
        if directory_watcher.last_modification > self.last_update {
            let new = Self::load(factory, &self.vertex_filenames, &self.pixel_filenames);
            self.last_update = Instant::now();
            if let Ok(shader_set) = new {
                self.shader_set = shader_set;
                return true;
            }
        }
        false
    }

    fn load<F: gfx::Factory<R>>(
        factory: &mut F,
        vertex_filenames: &Vec<PathBuf>,
        pixel_filenames: &Vec<PathBuf>,
    ) -> Result<gfx::ShaderSet<R>, Box<Error>> {
        let v = create_vertex_shader(
            factory,
            concat_file_contents(vertex_filenames.iter())?.as_bytes(),
        )?;
        let f = create_pixel_shader(
            factory,
            concat_file_contents(pixel_filenames.iter())?.as_bytes(),
        )?;
        Ok(gfx::ShaderSet::Simple(v, f))
    }
}