-
Notifications
You must be signed in to change notification settings - Fork 2
/
lib.rs
285 lines (252 loc) · 9.21 KB
/
lib.rs
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
pub extern crate hot_reloading_macros;
pub extern crate libloading;
use std::{any::TypeId, fs::metadata, path::PathBuf, time::Duration};
use bevy::{prelude::*, utils::Instant};
use libloading::Library;
/// Get info about HotReload state.
#[derive(Resource)]
pub struct HotReload {
pub updated_this_frame: bool,
pub last_update_time: Instant,
pub disable_reload: bool,
}
impl Default for HotReload {
fn default() -> Self {
HotReload {
updated_this_frame: false,
disable_reload: false,
last_update_time: Instant::now().checked_sub(Duration::from_secs(1)).unwrap(),
}
}
}
#[derive(Debug, Event)]
pub struct HotReloadEvent {
pub last_update_time: Instant,
}
/// Only for HotReload internal use. Must be pub because it is
/// inserted as an arg on systems with #[make_hot]
#[derive(Resource)]
pub struct HotReloadLibInternalUseOnly {
pub library: Option<Library>,
pub updated_this_frame: bool,
pub last_update_time: Instant,
pub cargo_watch_child: Option<ChildGuard>,
pub library_paths: LibPathSet,
}
pub struct HotReloadPlugin {
/// Start cargo watch with plugin
pub auto_watch: bool,
/// Use bevy_dylib feature with cargo watch
pub bevy_dylib: bool,
/// The name of the library target in Cargo.toml:
/// [lib]
/// name = "lib_your_project_name"
/// Defaults to your_project_name with lib_ prefix
/// This should be without .so or .dll
pub library_name: Option<String>,
}
impl Default for HotReloadPlugin {
fn default() -> Self {
HotReloadPlugin {
auto_watch: true,
bevy_dylib: true,
library_name: None,
}
}
}
impl Plugin for HotReloadPlugin {
fn build(&self, app: &mut App) {
#[cfg(feature = "bypass")]
{
app.add_event::<HotReloadEvent>()
.insert_resource(HotReload {
updated_this_frame: false,
disable_reload: true,
..default()
});
return;
}
let mut child = None;
let release_mode = false;
#[cfg(not(debug_assertions))]
let release_mode = true;
let library_paths = LibPathSet::new(self.library_name.clone()).unwrap();
if self.auto_watch {
let build_cmd = format!(
"build --lib --target-dir {} {} {}",
library_paths.folder.parent().unwrap().to_string_lossy(),
if release_mode { "--release" } else { "" },
if self.bevy_dylib {
"--features bevy/bevy_dylib"
} else {
""
}
);
child = Some(ChildGuard(
std::process::Command::new("cargo")
.arg("watch")
.arg("--postpone")
.arg("--watch-when-idle")
.arg("-w")
.arg("src")
.arg("-x")
.arg(build_cmd)
.spawn()
.expect("cargo watch command failed, make sure cargo watch is installed"),
));
}
// TODO move as early as possible
app.add_systems(PreUpdate, (update_lib, check_type_ids).chain())
//.add_system_to_stage(CoreStage::PostUpdate, clean_up_watch)
.add_event::<HotReloadEvent>()
.insert_resource(HotReloadLibInternalUseOnly {
cargo_watch_child: child,
library: None,
updated_this_frame: false,
// Using 1 second ago so to trigger lib load immediately instead of in 1 second
last_update_time: Instant::now().checked_sub(Duration::from_secs(1)).unwrap(),
library_paths,
})
.insert_resource(HoldTypeId(TypeId::of::<HoldTypeId>()))
.insert_resource(HotReload::default());
}
}
pub struct LibPathSet {
folder: PathBuf,
name: String,
extension: String,
}
impl LibPathSet {
fn new(library_name: Option<String>) -> Option<Self> {
if let Ok(lib_path) = std::env::current_exe() {
let name = library_name.unwrap_or({
let stem = lib_path.file_stem().unwrap();
format!("lib_{}", stem.to_str().unwrap())
});
let folder = lib_path.parent().unwrap();
#[cfg(unix)]
let extension = String::from("so");
#[cfg(windows)]
let extension = String::from("dll");
return Some(LibPathSet {
folder: (folder).to_path_buf(),
name,
extension,
});
}
None
}
/// File path the compiler outputs to
fn lib_file_path(&self) -> PathBuf {
self.folder.join(&self.name).with_extension(&self.extension)
}
/// File path copied to for hot reloads
fn hot_in_use_file_path(&self) -> PathBuf {
self.folder
.join(format!("{}_hot_in_use", self.name))
.with_extension(&self.extension)
}
/// File path copied to for initial run
fn main_in_use_file_path(&self) -> PathBuf {
self.folder
.join(format!("{}_main_in_use", self.name))
.with_extension(&self.extension)
}
}
fn update_lib(
mut hot_reload_int: ResMut<HotReloadLibInternalUseOnly>,
mut hot_reload: ResMut<HotReload>,
mut event: EventWriter<HotReloadEvent>,
) {
hot_reload_int.updated_this_frame = false;
hot_reload.updated_this_frame = false;
if hot_reload.disable_reload {
return;
}
let lib_file_path = hot_reload_int.library_paths.lib_file_path();
let hot_in_use_file_path = hot_reload_int.library_paths.hot_in_use_file_path();
// copy over and load lib if it has been updated, or hasn't been initially
if lib_file_path.is_file() {
if hot_in_use_file_path.is_file() {
let hot_lib_meta = metadata(&hot_in_use_file_path).unwrap();
let main_lib_meta = metadata(&lib_file_path).unwrap();
if hot_lib_meta.modified().unwrap() < main_lib_meta.modified().unwrap()
&& hot_reload_int.last_update_time.elapsed() > Duration::from_secs(1)
{
hot_reload_int.library = None;
let _ = std::fs::copy(lib_file_path, &hot_in_use_file_path);
}
} else {
hot_reload_int.library = None;
std::fs::copy(lib_file_path, &hot_in_use_file_path).unwrap();
}
if hot_reload_int.library.is_none() {
unsafe {
let lib = libloading::Library::new(&hot_in_use_file_path).unwrap_or_else(|_| {
panic!(
"Can't open required library {}",
&hot_in_use_file_path.to_string_lossy()
)
});
// TODO set globals like IoTaskPool here
hot_reload_int.library = Some(lib);
hot_reload_int.updated_this_frame = true;
hot_reload_int.last_update_time = Instant::now();
event.send(HotReloadEvent {
last_update_time: hot_reload_int.last_update_time,
});
}
}
}
hot_reload.updated_this_frame = hot_reload_int.updated_this_frame;
hot_reload.last_update_time = hot_reload_int.last_update_time;
}
pub struct ChildGuard(pub std::process::Child);
impl Drop for ChildGuard {
fn drop(&mut self) {
match self.0.kill() {
Err(e) => println!("Could not kill cargo watch process: {}", e),
Ok(_) => println!("Successfully killed cargo watch process"),
}
}
}
#[derive(Resource)]
struct HoldTypeId(TypeId);
mod ridiculous_bevy_hot_reloading {
pub use super::*;
}
#[hot_reloading_macros::make_hot]
fn check_type_ids(type_id: Res<HoldTypeId>, _hot_reload_int: Res<HotReloadLibInternalUseOnly>) {
if type_id.0 != TypeId::of::<HoldTypeId>() {
// If we include Res<HotReloadLibInternalUseOnly> the ChildGuard gets dropped
// Otherwise cargo watch keeps running
panic!(
"{}",
"ridiculous_bevy_hot_reloading: ERROR TypeIds \
do not match, this happens when the primary \
and dynamic libraries are not identically \
built. Make sure either both, or neither are \
using bevy_dylib"
);
}
}
/// Copies library file before running so the original can be overwritten
/// Only needed if using bevy_dylib
pub fn dyn_load_main(main_function_name: &str, library_name: Option<String>) {
if let Some(lib_paths) = LibPathSet::new(library_name) {
let lib_file_path = lib_paths.lib_file_path();
let main_in_use_file_path = lib_paths.main_in_use_file_path();
if lib_file_path.is_file() {
std::fs::copy(lib_file_path, &main_in_use_file_path).unwrap();
unsafe {
if let Ok(lib) = libloading::Library::new(main_in_use_file_path) {
let func: libloading::Symbol<unsafe extern "C" fn()> =
lib.get(main_function_name.as_bytes()).unwrap();
func();
}
}
} else {
panic!("Could not find library file {:?}", lib_file_path);
}
}
}