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;