hotline_rs/plugin.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
use crate::client::*;
use crate::gfx;
use crate::os;
use crate::reloader;
use std::process::ExitStatus;
use std::process::Command;
use std::io::{self, Write};
/// General dll plugin responder, will check for source code changes and run cargo build to re-build the library
pub struct PluginReloadResponder {
/// Name of the plugin
pub name: String,
/// Path to the plugins build director, where you would run `cargo build -p <name>`
pub path: String,
/// Full path to the build binary dylib or dll
pub output_filepath: String,
/// Array of source code files to track and check for changes
pub files: Vec<String>
}
/// Public trait for defining a plugin in a another library implement this trait and instantiate it with `hotline_plugin!`
pub trait Plugin<D: gfx::Device, A: os::App> {
/// Create a new instance of the plugin
fn create() -> Self where Self: Sized;
/// Called when the plugin is loaded and after a reload has happened, setup resources and state in here
fn setup(&mut self, client: Client<D, A>) -> Client<D, A>;
/// Called each and every frame, here put your update and render logic
fn update(&mut self, client: Client<D, A>) -> Client<D, A>;
// Called where it is safe to make imgui calls
fn ui(&mut self, client: Client<D, A>) -> Client<D, A>;
// Called when the plugin is to be unloaded, this will clean up
fn unload(&mut self, client: Client<D, A>) -> Client<D, A>;
}
/// Utility function to build all plugins, this can be used to bootstrap them if they don't exist
pub fn build_all() {
let path = super::get_data_path("..");
let output = if super::get_config_name() == "release" {
Command::new("cargo")
.current_dir(path)
.arg("build")
.arg("--release")
.output()
.expect("hotline::hot_lib:: hot lib failed to build!")
}
else {
Command::new("cargo")
.current_dir(path)
.arg("build")
.output()
.expect("hotline::hot_lib:: hot lib failed to build!")
};
if !output.stdout.is_empty() {
println!("{}", String::from_utf8(output.stdout).unwrap());
}
if !output.stderr.is_empty() {
println!("{}", String::from_utf8(output.stderr).unwrap());
}
}
/// Reload responder implementation for `PluginLib` uses cargo build, and hot lib reloader
impl reloader::ReloadResponder for PluginReloadResponder {
fn add_file(&mut self, path: &str) {
self.files.push(path.to_string());
}
fn get_files(&self) -> Vec<String> {
// scan for new files so we can dd them and pickup changes
// TODO; this could be more easily be configured in a plugin meta data file
let src_path = self.path.to_string() + "/" + &self.name.to_string() + "/src";
let src_files = super::get_files_recursive(&src_path, Vec::new());
let mut result = self.files.to_vec();
result.extend(src_files);
result
}
fn get_last_mtime(&self) -> std::time::SystemTime {
let meta = std::fs::metadata(&self.output_filepath);
if meta.is_ok() {
std::fs::metadata(&self.output_filepath).unwrap().modified().unwrap()
}
else {
std::time::SystemTime::now()
}
}
fn build(&mut self) -> ExitStatus {
let output = if super::get_config_name() == "release" {
Command::new("cargo")
.current_dir(&self.path)
.arg("build")
.arg("--release")
.arg("-p")
.arg(&self.name)
.output()
.expect("hotline::hot_lib:: hot lib failed to build!")
}
else {
Command::new("cargo")
.current_dir(&self.path)
.arg("build")
.arg("-p")
.arg(&self.name)
.output()
.expect("hotline::hot_lib:: hot lib failed to build!")
};
let mut stdout = io::stdout().lock();
if !output.stdout.is_empty() {
stdout.write_all(&output.stdout).unwrap();
}
if !output.stderr.is_empty() {
stdout.write_all(&output.stderr).unwrap();
}
output.status
}
}
/// Macro to instantiate a new hotline plugin, simply defined a concrete plugin type:
/// struct EmptyPlugin;
///
/// You can implement the `Plugin` trait for `EmptyPlugin`
/// impl Plugin<gfx_platform::Device, os_platform::App> for EmptyPlugin {
/// ..
/// }
///
/// Then use this macro to make the plugin loadable from a dll
/// hotline_plugin![EmptyPlugin];
#[macro_export]
macro_rules! hotline_plugin {
($input:ident) => {
/// Plugins are created on the heap and the instance is passed from the client to the plugin function calls
// c-abi wrapper for `Plugin::create`
#[no_mangle]
pub fn create() -> *mut core::ffi::c_void {
let plugin = $input::create();
let ptr = Box::into_raw(Box::new(plugin));
ptr.cast()
}
// c-abi wrapper for `Plugin::update`
#[no_mangle]
pub fn update(mut client: client::Client<gfx_platform::Device, os_platform::App>, ptr: *mut core::ffi::c_void) -> client::Client<gfx_platform::Device, os_platform::App> {
unsafe {
let plugin = ptr.cast::<$input>();
let plugin = plugin.as_mut().unwrap();
plugin.update(client)
}
}
// c-abi wrapper for `Plugin::setup`
#[no_mangle]
pub fn setup(mut client: client::Client<gfx_platform::Device, os_platform::App>, ptr: *mut core::ffi::c_void) -> client::Client<gfx_platform::Device, os_platform::App> {
unsafe {
let plugin = ptr.cast::<$input>();
let plugin = plugin.as_mut().unwrap();
plugin.setup(client)
}
}
// c-abi wrapper for `Plugin::reload`
#[no_mangle]
pub fn unload(mut client: client::Client<gfx_platform::Device, os_platform::App>, ptr: *mut core::ffi::c_void) -> client::Client<gfx_platform::Device, os_platform::App> {
unsafe {
let plugin = ptr.cast::<$input>();
let plugin = plugin.as_mut().unwrap();
plugin.unload(client)
}
}
// c-abi wrapper for `Plugin::reload`
#[no_mangle]
pub fn ui(mut client: client::Client<gfx_platform::Device, os_platform::App>, ptr: *mut core::ffi::c_void, imgui_ctx: *mut core::ffi::c_void) -> client::Client<gfx_platform::Device, os_platform::App> {
unsafe {
let plugin = ptr.cast::<$input>();
let plugin = plugin.as_mut().unwrap();
client.imgui.set_current_context(imgui_ctx);
plugin.ui(client)
}
}
}
}
/// Plugin instances are crated by the `Plugin::create` function, created on the heap
/// and passed around as a void* through the hotline_plugin macro to become a `Plugin` trait
pub type PluginInstance = *mut core::ffi::c_void;