live_reload/lib.rs
1#![deny(missing_docs)]
2
3//! A library for doing live-reloading game development.
4//!
5//! This is inspired by the article ["Interactive Programming in C"][] by Chris
6//! Wellons, and the video ["Loading Game Code Dynamically"][] from Handmade
7//! Hero by Casey Muratori.
8//!
9//! ["Interactive Programming in C"]: http://nullprogram.com/blog/2014/12/23/
10//! ["Loading Game Code Dynamically"]: https://www.youtube.com/watch?v=WMSBRk5WG58
11//!
12//! The general idea is that your main host program is a wrapper around a
13//! dynamic library that does all the interesting work of your game. This means
14//! that you can simply reload the library while the game is still running, and
15//! have your game update live. As a consequence however, you can't have any
16//! global state in your library, everything must be owned by the host in order
17//! to avoid getting unloaded with the library.
18//!
19//! In order to call back into the host program, you specify a `Host` type
20//! containing function pointers to services in the host program. This `Host`
21//! struct will be passed along with your program state. The `Host` type should
22//! always be defined in a separate module so that both the host program and the
23//! reloadable library use a consistent host type. The current recommended
24//! method is to define your host type in its own module. Then use that module
25//! from both the host and the reloadable library. If your project organization
26//! puts the common module in a parent, you can always use the `#[path=...]`
27//! meta on the module, for example:
28//!
29//! ```rust,ignore
30//! #[path="../host_api.rs"]
31//! mod host_api;
32//! ```
33//!
34//! While designing your host and library, keep in mind the role of the two
35//! communication types:
36//!
37//! - State is isolated to the reloadable library, the main program knows
38//! nothing about it except for its size so that it can keep it allocated.
39//! This lets you change the contents of the State struct without restarting
40//! the whole program. This is intended to handle the state of the game world,
41//! independent of the host program.
42//!
43//! - Host is defined by the host program and the layout is visible to both
44//! sides of the bridge. This means that it has to remain the same during a
45//! run of the game engine. This should hold resources that can only be
46//! produced by the host program, and function pointers to services that can
47//! only be provided by the host program. (Anything that requires global state
48//! like system allocators, graphics contexts, input handling, etc etc.)
49//!
50//! See the Host Example and Library Example sections for instructions on how to
51//! build a reloadable application.
52//!
53//! # Host Example
54//!
55//! A program that hosts a reloadable library will need to load the library, and
56//! then periodically reload it. The [`Reloadable`][] automatically installs a
57//! filesystem watcher for you so that it knows when the library file has been
58//! updated or replaced, and the [`reload`][] method will only actually perform
59//! a reload if the file has changed. The core of your main loop will therefore
60//! usually look something like this:
61//!
62//! ```rust,no_run
63//! use std::thread;
64//!
65//! mod host_api {
66//! // This should always be in a different file
67//! pub struct HostApi {
68//! pub print: fn(&str),
69//! }
70//! }
71//! use host_api::HostApi;
72//!
73//! type App = live_reload::Reloadable<HostApi>;
74//!
75//! fn print(msg: &str) {
76//! print!("{}", msg);
77//! }
78//!
79//! fn main() {
80//! let mut prog = App::new(
81//! "target/debug/libreload.dylib",
82//! HostApi { print: print },
83//! ).expect("Should successfully load");
84//! 'main: loop {
85//! if prog.update() == live_reload::ShouldQuit::Yes {
86//! break 'main;
87//! }
88//! prog.reload().expect("Should successfully reload");
89//! }
90//! }
91//! ```
92//!
93//! # Library Example
94//!
95//! A live-reloadable library needs to register its entry-points so that the
96//! host program can find them. The [`live_reload!`][] macro lets you do this
97//! conveniently.
98//!
99//! The lifecycle of your reloadable library will happen in a few stages:
100//!
101//! - `init` gets called at the very beginning of the program, when the host
102//! starts for the first time.
103//! - `reload` gets called on each library load, including the first time. This
104//! should be usually empty, but when you're in development, you might want to
105//! reset things here, or migrate data, or things like that. The pointer
106//! you're passed will refer to the same struct that you had when the previous
107//! library was unloaded, so it might not be properly initialized. You should
108//! try to make your struct be `#[repr(C)]`, and only add members at the end
109//! to minimize the problems of reloading.
110//! - `update` gets called at the host program's discretion. You'll probably end
111//! up calling this once per frame. In addition to doing whatever work you
112//! were interested in, `update` also returns a value indicating whether the
113//! host program should quit.
114//! - `unload` gets called before a library unloads. This will probably be empty
115//! even more often than `reload`, but you might need it for some debugging or
116//! data migration purpose.
117//! - `deinit` gets called when the host program is actually shutting down--it's
118//! called on the drop of the [`Reloadable`][].
119//!
120//! Here's an example of a live-reloadable library that handles a counter.
121//!
122//! ```rust
123//! #[macro_use] extern crate live_reload;
124//! # fn main() {}
125//! use live_reload::ShouldQuit;
126//!
127//! mod host_api {
128//! // This should always be in a different file.
129//! pub struct Host {
130//! pub print: fn(&str),
131//! }
132//! }
133//!
134//! use host_api::Host;
135//!
136//! live_reload! {
137//! host: Host;
138//! state: State;
139//! init: my_init;
140//! reload: my_reload;
141//! update: my_update;
142//! unload: my_unload;
143//! deinit: my_deinit;
144//! }
145//!
146//! struct State {
147//! counter: u64,
148//! }
149//!
150//! fn my_init(host: &mut Host, state: &mut State) {
151//! state.counter = 0;
152//! (host.print)("Init! Counter: 0.");
153//! }
154//!
155//! fn my_reload(host: &mut Host, state: &mut State) {
156//! (host.print)(&format!("Reloaded at {}.", state.counter));
157//! }
158//!
159//! fn my_update(host: &mut Host, state: &mut State) -> ShouldQuit {
160//! state.counter += 1;
161//! (host.print)(&format!("Counter: {}.", state.counter));
162//! ShouldQuit::No
163//! }
164//!
165//! fn my_unload(host: &mut Host, state: &mut State) {
166//! (host.print)(&format!("Unloaded at {}.", state.counter));
167//! }
168//!
169//! fn my_deinit(host: &mut Host, state: &mut State) {
170//! (host.print)(&format!("Goodbye! Reached a final value of {}.", state.counter));
171//! }
172//! ```
173//!
174//! [`Reloadable`]: struct.Reloadable.html
175//! [`reload`]: struct.Reloadable.html#method.reload
176//! [`live_reload!`]: macro.live_reload.html
177
178extern crate notify;
179extern crate libloading;
180
181use std::path::{Path, PathBuf};
182use std::time::Duration;
183use std::sync::mpsc::{channel, Receiver};
184
185use notify::{Watcher, RecommendedWatcher};
186use libloading::Library;
187
188#[cfg(unix)]
189type Symbol<T> = libloading::os::unix::Symbol<T>;
190#[cfg(windows)]
191type Symbol<T> = libloading::os::windows::Symbol<T>;
192
193struct AppSym<Host> {
194 /// This needs to be present so that the library will be closed on drop
195 _lib: Library,
196 api: Symbol<*mut internals::ReloadApi<Host>>,
197}
198
199// @Todo: Flesh out this documentation
200/// A `Reloadable` represents a handle to library that can be live reloaded.
201pub struct Reloadable<Host> {
202 path: PathBuf,
203 sym: Option<AppSym<Host>>,
204 state: Vec<u64>,
205 _watcher: RecommendedWatcher,
206 rx: Receiver<notify::DebouncedEvent>,
207 host: Host,
208}
209
210/// The errors that can occur while working with a `Reloadable` object.
211#[derive(Debug)]
212pub enum Error {
213 /// An I/O error occurred while trying to load or reload the library. This
214 /// can indicate that the file is missing, or that the library didn't have
215 /// the expected `RELOAD_API` symbol.
216 // @Diagnostics: Add an error type to distinguish this latter situation
217 Io(std::io::Error),
218 /// An error occurred while creating the filesystem watcher.
219 Watch(notify::Error),
220 /// The `Host` type of the host and library don't match.
221 MismatchedHost,
222}
223
224impl From<std::io::Error> for Error {
225 fn from(err: std::io::Error) -> Error {
226 Error::Io(err)
227 }
228}
229
230impl From<notify::Error> for Error {
231 fn from(err: notify::Error) -> Error {
232 Error::Watch(err)
233 }
234}
235
236impl std::fmt::Display for Error {
237 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
238 write!(fmt, "{:?}", self)
239 }
240}
241
242impl std::error::Error for Error {
243 fn description(&self) -> &str {
244 match *self {
245 Error::Io(ref err) => err.description(),
246 Error::Watch(ref err) => err.description(),
247 Error::MismatchedHost => "mismatch between host and library's Host types",
248 }
249 }
250}
251
252impl<Host> AppSym<Host> {
253 fn new<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
254 let library = Library::new(path.as_ref())?;
255 let api = unsafe {
256 library
257 .get::<*mut internals::ReloadApi<Host>>(b"RELOAD_API")?
258 .into_raw()
259 };
260 Ok(AppSym {
261 _lib: library,
262 api: api,
263 })
264 }
265}
266
267impl<Host> Reloadable<Host> {
268 /// Create a new Reloadable library.
269 ///
270 /// This takes the path to a dynamic library containing a `RELOAD_API`
271 /// symbol that exports the functions needed for live reloading. In order to
272 /// define this symbol in your own reloadable libraries, see the
273 /// [`live_reload!`][] macro. This will load the library and initialize a
274 /// filesystem watcher pointing to the file in order to know when the
275 /// library has changed.
276 ///
277 /// [`live_reload!`]: macro.live_reload.html
278 pub fn new<P: AsRef<Path>>(path: P, host: Host) -> Result<Self, Error> {
279 let sym = AppSym::new(&path)?;
280 let size = (unsafe { &**sym.api }.size)();
281 let (tx, rx) = channel();
282 let mut watcher = notify::watcher(tx, Duration::from_secs(1))?;
283 let mut new_path = PathBuf::new();
284 new_path.push(path);
285 watcher.watch(
286 new_path.parent().unwrap(),
287 notify::RecursiveMode::NonRecursive,
288 )?;
289 let mut app = Reloadable {
290 path: new_path.canonicalize()?,
291 sym: Some(sym),
292 state: Vec::new(),
293 _watcher: watcher,
294 rx: rx,
295 host: host,
296 };
297 app.realloc_buffer(size);
298 if let Some(AppSym { ref mut api, .. }) = app.sym {
299 (unsafe { &***api }.init)(&mut app.host, Self::get_state_ptr(&mut app.state));
300 }
301 Ok(app)
302 }
303
304 /// Reload the library if it has changed, otherwise do nothing.
305 ///
306 /// This will consult with the filesystem watcher, and if the library has
307 /// been recreated or updated, it will reload the library. See
308 /// [`reload_now`][] for details on what happens when a library is reloaded.
309 ///
310 /// [`reload_now`]: struct.Reloadable.html#method.reload_now
311 pub fn reload(&mut self) -> Result<(), Error> {
312 let mut should_reload = false;
313 while let Ok(evt) = self.rx.try_recv() {
314 use notify::DebouncedEvent::*;
315 match evt {
316 NoticeWrite(ref path) |
317 Write(ref path) |
318 Create(ref path) => {
319 if *path == self.path {
320 should_reload = true;
321 }
322 }
323 _ => {}
324 }
325 }
326
327 if should_reload || self.sym.is_none() {
328 self.reload_now()
329 } else {
330 Ok(())
331 }
332 }
333
334 /// Immediately reload the library without checking whether it has changed.
335 ///
336 /// This first calls `unload` on the currently loaded library, then unloads
337 /// the dynamic library. Next, it loads the new dynamic library, and calls
338 /// `reload` on that. If the new library fails to load, this method will
339 /// return an `Err` and the `Reloadable` will be left with no library
340 /// loaded.
341 ///
342 /// [`update`]: struct.Reloadable.html#method.update
343 pub fn reload_now(&mut self) -> Result<(), Error> {
344 if let Some(AppSym { ref mut api, .. }) = self.sym {
345 (unsafe { &***api }.unload)(&mut self.host, Self::get_state_ptr(&mut self.state));
346 }
347 self.sym = None;
348 let sym = AppSym::new(&self.path)?;
349 // @Avoid reallocating if unnecessary
350 self.realloc_buffer((unsafe { &**sym.api }.size)());
351 (unsafe { &**sym.api }.reload)(&mut self.host, Self::get_state_ptr(&mut self.state));
352 self.sym = Some(sym);
353
354 Ok(())
355 }
356
357 /// Call the update method on the library.
358 ///
359 /// If no library is currently loaded, this does nothing and returns
360 /// [`ShouldQuit::No`](enum.ShouldQuit.html#).
361 pub fn update(&mut self) -> ShouldQuit {
362 if let Some(AppSym { ref mut api, .. }) = self.sym {
363 (unsafe { &***api }.update)(&mut self.host, Self::get_state_ptr(&mut self.state))
364 } else {
365 ShouldQuit::No
366 }
367 }
368
369 /// Reallocate the buffer used to store the `State`.
370 fn realloc_buffer(&mut self, size: usize) {
371 let alloc_size_u64s = (size + 7) / 8;
372 self.state.resize(alloc_size_u64s, 0);
373 }
374
375 /// Get a void pointer to the `State` buffer.
376 fn get_state_ptr(buffer: &mut Vec<u64>) -> *mut () {
377 buffer.as_mut_ptr() as *mut ()
378 }
379
380 /// Get a reference to the `Host` struct>
381 pub fn host(&self) -> &Host { &self.host }
382
383 /// Get a mutable reference to the `Host` struct.
384 pub fn host_mut(&mut self) -> &mut Host { &mut self.host }
385}
386
387impl<Host> Drop for Reloadable<Host> {
388 fn drop(&mut self) {
389 if let Some(AppSym { ref mut api, .. }) = self.sym {
390 unsafe {
391 ((***api).deinit)(&mut self.host, Self::get_state_ptr(&mut self.state));
392 }
393 }
394 }
395}
396
397
398/// Should the main program quit? More self-documenting than a boolean!
399///
400/// This type is returned by the [`update`][] method, since with a boolean it's
401/// often unclear if `true` means "should continue" or "should quit".
402///
403/// [`update`]: struct.Reloadable.html#method.update
404#[derive(Debug, PartialEq, Eq)]
405pub enum ShouldQuit {
406 /// The wrapped library thinks the main program should continue running.
407 No = 0,
408 /// The wrapped library thinks the main program should quit now.
409 Yes = 1,
410}
411
412/// Exported for compilation reasons but not useful, only look if you're curious.
413///
414/// This module holds to the `ReloadApi` struct, which is what what is looked up
415/// by the `Reloadable` in order to communicate with the reloadable library. It
416/// needs to be exported in order to avoid forcing the type definition into the
417/// pub symbols of the wrapped library. An instance of `ReloadApi` called
418/// `RELOAD_API` is generated by the [`live_reload!`][] macro.
419///
420/// [`live_reload!`]: ../macro.live_reload.html
421pub mod internals {
422 /// Contains function pointers for all the parts of the reloadable object lifecycle.
423 #[repr(C)]
424 pub struct ReloadApi<Host> {
425 /// Returns the size of the State struct so that the host can allocate
426 /// space for it.
427 pub size: fn() -> usize,
428 /// Initializes the State struct when the program is first started.
429 pub init: fn(&mut Host, *mut ()),
430 /// Makes any necessary updates when the program is reloaded.
431 ///
432 /// This will probably be normally empty. If you changed the State
433 /// struct since the last compile, then it won't necessarily be
434 /// correctly initialized. For safety, you should make your State struct
435 /// `#[repr(C)]` and only add members at the end.
436 pub reload: fn(&mut Host, *mut ()),
437 /// Update the
438 pub update: fn(&mut Host, *mut ()) -> super::ShouldQuit,
439 /// Prepare for the library to be unloaded before a new version loads.
440 ///
441 /// This will probably normally be empty except for short periods in
442 /// development when you're making lots of live changes and need to do
443 /// some kind of migration.
444 pub unload: fn(&mut Host, *mut ()),
445 /// Do final shutdowns before the program completely quits.
446 pub deinit: fn(&mut Host, *mut ()),
447 }
448}
449
450/// Declare the API functions for a live-reloadable library.
451///
452/// This generates wrappers around higher-level lifecycle functions, and then
453/// exports them in a struct that the reloader can find.
454///
455/// You need to to specify the host API type, define a struct that represents
456/// the state of your program, and then define methods for `init`, `reload`,
457/// `update`, `unload`, and `deinit`. `init` and `deinit` are called at the very
458/// beginning and end of the program, and `reload` and `unload` are called
459/// immediately after and before the library is loaded/reloaded. `update` is
460/// called by the wrapping application as needed.
461///
462/// # Example
463///
464/// ```rust
465/// # #[macro_use] extern crate live_reload;
466/// # fn main() {}
467/// # #[repr(C)] struct State {}
468/// # mod host_api { pub struct Host; }
469/// # use host_api::Host;
470/// # fn my_init(_: &mut Host, _: &mut State) {}
471/// # fn my_reload(_: &mut Host, _: &mut State) {}
472/// # fn my_unload(_: &mut Host, _: &mut State) {}
473/// # fn my_deinit(_: &mut Host, _: &mut State) {}
474/// # use live_reload::ShouldQuit;
475/// # fn my_update(_: &mut Host, _: &mut State) -> ShouldQuit { ShouldQuit::No }
476/// live_reload! {
477/// host: host_api::Host;
478/// state: State;
479/// init: my_init;
480/// reload: my_reload;
481/// update: my_update;
482/// unload: my_unload;
483/// deinit: my_deinit;
484/// }
485/// ```
486#[macro_export]
487macro_rules! live_reload {
488 (host: $Host:ty;
489 state: $State:ty;
490 init: $init:ident;
491 reload: $reload:ident;
492 update: $update:ident;
493 unload: $unload:ident;
494 deinit: $deinit:ident;) => {
495
496 fn cast<'a>(raw_state: *mut ()) -> &'a mut $State {
497 unsafe { &mut *(raw_state as *mut $State) }
498 }
499
500 fn init_wrapper(host: &mut $Host, raw_state: *mut ()) {
501 $init(host, cast(raw_state))
502 }
503
504 fn reload_wrapper(host: &mut $Host, raw_state: *mut ()) {
505 $reload(host, cast(raw_state))
506 }
507
508 fn update_wrapper(host: &mut $Host, raw_state: *mut ())
509 -> ::live_reload::ShouldQuit
510 {
511 $update(host, cast(raw_state))
512 }
513
514 fn unload_wrapper(host: &mut $Host, raw_state: *mut ()) {
515 $unload(host, cast(raw_state))
516 }
517
518 fn deinit_wrapper(host: &mut $Host, raw_state: *mut ()) {
519 $deinit(host, cast(raw_state))
520 }
521
522 #[no_mangle]
523 pub static RELOAD_API: ::live_reload::internals::ReloadApi<$Host> =
524 ::live_reload::internals::ReloadApi
525 {
526 size: ::std::mem::size_of::<$State>,
527 init: init_wrapper,
528 reload: reload_wrapper,
529 update: update_wrapper,
530 unload: unload_wrapper,
531 deinit: deinit_wrapper,
532 };
533 }
534}