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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use std::process::ExitStatus;
use std::time::SystemTime;
use std::time::Duration;
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;
/// Basic Reloader which can check timestamps on files and then callback functions supplied by the reload responder
pub struct Reloader {
/// lock may be written / modified on different threads
lock: Arc<Mutex<ReloadState>>,
/// You can implement your own `ReloadResponder` trait to get callback functions to trigger a build
responder: Arc<Mutex<Box<dyn ReloadResponder>>>,
/// indicates reloading is available / happening
hot: bool
}
/// Query reload status with a responder:
/// if
#[derive(PartialEq, Clone, Copy)]
pub enum ReloadState {
/// No action needs taking
None,
/// Changes detected build has triggered
Building,
/// There is a reload available, get into a stable state and call `complete_reload` to complete
Available,
}
/// Trait to be implemented for custom reloader responses
pub trait ReloadResponder: Send + Sync {
/// Add a file which is tracked and the time stamp compared for changes
fn add_file(&mut self, path: &str);
/// Returns a vector of files which are currently being tracked
fn get_files(&self) -> Vec<String>;
/// Retuns the current modified time of the built resource
fn get_last_mtime(&self) -> SystemTime;
/// Called when a tracked file is modified more recently than get_base_mtime
fn build(&mut self) -> ExitStatus;
}
impl Reloader {
/// Create a new instance of a reload with the designated ReloadResponder and start waiting for file changes
pub fn create(responder: Box<dyn ReloadResponder>) -> Self {
Self {
lock: Arc::new(Mutex::new(ReloadState::None)),
responder: Arc::new(Mutex::new(responder)),
hot: false
}.start()
}
/// if is_hot is true, then reloading is detected and in progress
pub fn is_hot(&self) -> bool {
self.hot
}
/// Add files to check in a thread safe manner
pub fn add_file(&mut self, path: &str) {
let mut responder = self.responder.lock().unwrap();
responder.add_file(path);
}
/// Start watching for and invoking reload changes, this will spawn threads to watch files
pub fn start(self) -> Self {
self.file_watcher_thread();
self
}
/// Call this each frame, if ReloadResult::Reload you must then clean up any data in preperation for a reload
pub fn check_for_reload(&mut self) -> ReloadState {
let lock = self.lock.lock().unwrap();
if *lock != ReloadState::None {
self.hot = true
}
*lock
}
/// Once data is cleaned up and it is safe to proceed this functions must be called
pub fn complete_reload(&mut self) {
let mut lock = self.lock.lock().unwrap();
// signal it is safe to proceed and reload the new code
*lock = ReloadState::None;
self.hot = false;
drop(lock);
println!("hotline_rs::reloader: reload complete");
}
/// Returns the latest timestamp of all the files tracked by the reloader
fn file_watcher_thread_check_mtime(responder: &Arc<Mutex<Box<dyn ReloadResponder>>>, cur_mtime: SystemTime) -> SystemTime {
let responder = responder.lock().unwrap();
let files = responder.get_files();
for file in &files {
let filepath = super::get_data_path(file);
let meta = std::fs::metadata(&filepath);
if meta.is_ok() {
let mtime = std::fs::metadata(&filepath).unwrap().modified().unwrap();
if mtime > cur_mtime {
return mtime;
}
}
else {
print!("hotline_rs::reloader: {filepath} not found!")
}
};
cur_mtime
}
/// Background thread will watch for changed filestamps among the registered files from the responder
fn file_watcher_thread(&self) {
let lock = self.lock.clone();
let mut cur_mtime = SystemTime::now();
let mut first_time_check = true;
let responder = self.responder.clone();
thread::Builder::new().name("hotline_rs::reloader::file_watcher_thread".to_string()).spawn(move || {
loop {
// check base mtime of the output lib, it might be old / stale when we run with a fresh client
if first_time_check {
cur_mtime = responder.lock().unwrap().get_last_mtime();
first_time_check = false;
}
let mtime = Self::file_watcher_thread_check_mtime(&responder, cur_mtime);
if mtime > cur_mtime {
// signal we are building (this might take a while)
let mut a = lock.lock().unwrap();
println!("hotline_rs::reloader: changes detected, building");
*a = ReloadState::Building;
drop(a);
let mut responder = responder.lock().unwrap();
if responder.build().success() {
// signal reload is ready
let mut a = lock.lock().unwrap();
println!("hotline_rs::reloader: build success, reload available");
*a = ReloadState::Available;
drop(a);
}
else {
println!("hotline_rs::reloader: build failed");
}
cur_mtime = mtime;
}
std::thread::sleep(Duration::from_millis(16));
}
}).unwrap();
}
}