use std::{path::PathBuf, sync::mpsc::Receiver};
use crate::prelude::*;
use anyhow::Result;
use libloading::Library;
use notify::Watcher;
const DEFAULT_WATCH_PATH: &str = "src";
const FN_CREATE: &[u8] = b"game_create";
const FN_INIT: &[u8] = b"game_init";
const FN_UPDATE: &[u8] = b"game_update";
const FN_DRAW: &[u8] = b"game_draw";
const FN_CLEANUP: &[u8] = b"game_cleanup";
#[cfg(unix)]
use libloading::os::unix::*;
#[cfg(windows)]
use libloading::os::windows::*;
type FnCreate = unsafe extern "C" fn() -> *mut std::ffi::c_void;
type FnInit = unsafe extern "C" fn(*mut std::ffi::c_void, *mut Engine);
type FnUpdate = unsafe extern "C" fn(*mut std::ffi::c_void, *mut Engine);
type FnDraw = unsafe extern "C" fn(*mut std::ffi::c_void, *mut Engine);
type FnCleanup = unsafe extern "C" fn(*mut std::ffi::c_void, *mut Engine);
struct SymbolTable {
lib: Library,
create: Symbol<FnCreate>,
init: Symbol<FnInit>,
update: Symbol<FnUpdate>,
draw: Symbol<FnDraw>,
cleanup: Symbol<FnCleanup>,
}
impl SymbolTable {
pub fn load(lib_path: &PathBuf) -> Result<Self> {
unsafe {
let lib = Library::new(lib_path)?;
let create = lib.get::<FnCreate>(FN_CREATE)?.into_raw();
let init = lib.get::<FnInit>(FN_INIT)?.into_raw();
let update = lib.get::<FnUpdate>(FN_UPDATE)?.into_raw();
let draw = lib.get::<FnDraw>(FN_DRAW)?.into_raw();
let cleanup = lib.get::<FnCleanup>(FN_CLEANUP)?.into_raw();
let st = SymbolTable {
lib,
create,
init,
update,
draw,
cleanup,
};
Ok(st)
}
}
pub fn unload(self) -> Result<()> {
self.lib.close()?;
Ok(())
}
}
pub struct HotReloader {
lib_path: PathBuf,
table: Option<SymbolTable>,
game: *mut std::ffi::c_void,
watcher_rx: Receiver<notify::Result<notify::Event>>,
#[allow(dead_code)]
watcher: notify::INotifyWatcher,
}
impl HotReloader {
pub fn new(lib_path: PathBuf) -> Result<Self> {
let (tx, rx) = std::sync::mpsc::channel();
let mut watcher = notify::RecommendedWatcher::new(tx, notify::Config::default())?;
let path: PathBuf = DEFAULT_WATCH_PATH.into();
watcher.watch(&path, notify::RecursiveMode::Recursive)?;
log::info!("Start watching file changes: {path:?}");
let table = SymbolTable::load(&lib_path)?;
let game = unsafe { (*table.create)() };
Ok(Self {
lib_path,
table: Some(table),
game,
watcher_rx: rx,
watcher,
})
}
pub fn compile(&self) -> Result<()> {
let output = std::process::Command::new("cargo")
.args(["build"])
.output()?;
if !output.status.success() {
let err = String::from_utf8(output.stderr)?;
log::error!("Failed to compile \n{err}");
}
Ok(())
}
pub fn reload(&mut self) -> Result<()> {
if let Some(table) = self.table.take() {
table.unload()?;
}
self.table = Some(SymbolTable::load(&self.lib_path)?);
Ok(())
}
fn try_hotreload(&mut self) {
let mut changed = false;
while let Ok(res) = self.watcher_rx.try_recv() {
match res {
Ok(event) => {
log::info!("Change: {event:?}");
if matches!(event.kind, notify::EventKind::Modify(..)) {
changed = true;
}
}
Err(error) => log::error!("Error: {error:?}"),
}
}
if changed {
if let Err(err) = self.compile() {
log::error!("Compile failed {err:?}");
return;
}
if let Err(err) = self.reload() {
log::error!("Reload failed {err:?}");
}
}
}
}
impl Scene for HotReloader {
fn init(&mut self, g: &mut Engine) {
unsafe {
if let Some(table) = self.table.as_ref() {
(*table.init)(self.game, g);
}
}
}
fn update(&mut self, g: &mut Engine) {
self.try_hotreload();
unsafe {
if let Some(table) = self.table.as_ref() {
(*table.update)(self.game, g);
}
}
}
fn draw(&mut self, g: &mut Engine) {
unsafe {
if let Some(table) = self.table.as_ref() {
(*table.draw)(self.game, g);
}
}
}
fn cleanup(&mut self, g: &mut Engine) {
unsafe {
if let Some(table) = self.table.as_ref() {
(*table.cleanup)(self.game, g);
}
}
}
}