1use crate::gfx;
2use crate::gfx::ReadBackRequest;
3use crate::os;
4use crate::imgui;
5use crate::plugin::PluginInstance;
6use crate::pmfx;
7use crate::imdraw;
8use crate::primitives;
9use crate::plugin;
10use crate::reloader;
11use crate::image;
12
13use gfx::{SwapChain, CmdBuf, Texture, RenderPass, Heap};
14
15use os::Window;
16use imgui::UserInterface;
17use plugin::PluginReloadResponder;
18use reloader::Reloader;
19
20use serde::{Deserialize, Serialize};
21
22use std::path::PathBuf;
23use std::collections::{HashMap, VecDeque};
24use std::time::SystemTime;
25
26use maths_rs::vec::vec4f;
27
28const STATUS_BAR_HEIGHT : f32 = 10.0;
29
30pub struct HotlineInfo {
32 pub name: String,
34 pub window_rect: os::Rect<i32>,
36 pub dpi_aware: bool,
38 pub clear_colour: Option<gfx::ClearColour>,
40 pub adapter_name: Option<String>,
42 pub num_buffers: u32,
44 pub shader_heap_size: usize,
46 pub render_target_heap_size: usize,
48 pub depth_stencil_heap_size: usize,
50 pub user_config: Option<UserConfig>
52}
53
54#[derive(Clone)]
56pub struct Time {
57 pub delta: f32,
59 pub raw_delta: f32,
61 pub update_delta: f32,
63 pub accumulated: f32,
65 pub smooth_delta: f32,
67 pub frame_start: SystemTime,
69 pub paused: bool,
71 pub delta_paused: bool,
73 pub time_scale: f32,
75 fixed_delta: Option<f32>
77}
78const SMOOTH_DELTA_FRAMES : usize = 120;
79
80impl Time {
82 fn new() -> Self {
84 Self {
85 delta: 0.0,
86 raw_delta: 0.0,
87 update_delta: 0.0,
88 accumulated: 0.0,
89 smooth_delta: 0.0,
90 frame_start: SystemTime::now(),
91 paused: false,
92 delta_paused: false,
93 time_scale: 1.0,
94 fixed_delta: None
95 }
96 }
97}
98
99impl Default for HotlineInfo {
101 fn default() -> Self {
102 HotlineInfo {
103 name: "hotline".to_string(),
104 window_rect: os::Rect {
105 x: 0,
106 y: 0,
107 width: 1280,
108 height: 1300
109 },
110 dpi_aware: true,
111 clear_colour: Some(gfx::ClearColour {
112 r: 0.45,
113 g: 0.55,
114 b: 0.60,
115 a: 1.00,
116 }),
117 num_buffers: 2,
118 adapter_name: None,
119 shader_heap_size: 1024,
120 render_target_heap_size: 128,
121 depth_stencil_heap_size: 64,
122 user_config: None
123 }
124 }
125}
126
127pub struct Client<D: gfx::Device, A: os::App> {
129 pub app: A,
130 pub device: D,
131 pub main_window: A::Window,
132 pub swap_chain: D::SwapChain,
133 pub pmfx: pmfx::Pmfx<D>,
134 pub cmd_buf: D::CmdBuf,
135 pub imdraw: imdraw::ImDraw<D>,
136 pub imgui: imgui::ImGui<D, A>,
137 pub unit_quad_mesh: pmfx::Mesh<D>,
138 pub user_config: UserConfig,
139 pub time: Time,
140 pub libs: HashMap<String, hot_lib_reloader::LibReloader>,
141 plugins: Vec<PluginCollection>,
142 delta_history: VecDeque<f32>,
143 instance_name: String,
144 status_bar_height: f32
145}
146
147#[derive(Serialize, Deserialize, Clone)]
149pub struct PluginInfo {
150 pub path: String
151}
152
153#[derive(Serialize, Deserialize, Clone)]
155pub struct UserConfig {
156 pub main_window_rect: os::Rect<i32>,
158 pub console_window_rect: Option<os::Rect<i32>>,
159 pub plugins: Option<HashMap<String, PluginInfo>>,
160 pub plugin_data: Option<HashMap<String, serde_json::Value>>
161}
162
163#[derive(PartialEq, Eq)]
165enum PluginState {
166 None,
167 Reload,
168 Setup,
169 Unload,
170}
171
172struct PluginCollection {
174 name: String,
175 reloader: reloader::Reloader,
176 instance: PluginInstance,
177 state: PluginState
178}
179
180impl<D, A> Client<D, A> where D: gfx::Device, A: os::App, D::RenderPipeline: gfx::Pipeline {
182 pub fn create(info: HotlineInfo) -> Result<Self, super::Error> {
184 let user_config_path = super::get_data_path("../user_config.json");
186 let saved_user_config = if std::path::Path::new(&user_config_path).exists() {
187 let user_data = std::fs::read(user_config_path)?;
188 serde_json::from_slice(&user_data).unwrap()
189 }
190 else {
191 UserConfig {
192 main_window_rect: info.window_rect,
193 console_window_rect: None,
194 plugin_data: Some(HashMap::new()),
195 plugins: None
196 }
197 };
198
199 let user_config = info.user_config.unwrap_or(saved_user_config);
201
202 let mut app = A::create(os::AppInfo {
204 name: info.name.to_string(),
205 num_buffers: info.num_buffers,
206 dpi_aware: info.dpi_aware,
207 window: false,
208 });
209 if let Some(console_rect) = user_config.console_window_rect {
210 app.set_console_window_rect(console_rect);
211 }
212
213 let mut device = D::create(&gfx::DeviceInfo {
215 adapter_name: info.adapter_name,
216 shader_heap_size: info.shader_heap_size,
217 render_target_heap_size: info.render_target_heap_size,
218 depth_stencil_heap_size: info.depth_stencil_heap_size,
219 });
220
221 let main_window = app.create_window(os::WindowInfo {
223 title: info.name.to_string(),
224 rect: user_config.main_window_rect,
225 style: os::WindowStyleFlags::NONE,
226 parent_handle: None,
227 });
228
229 let swap_chain_info = gfx::SwapChainInfo {
231 num_buffers: info.num_buffers,
232 format: gfx::Format::RGBA8n,
233 clear_colour: info.clear_colour
234 };
235 let mut swap_chain = device.create_swap_chain::<A>(&swap_chain_info, &main_window)?;
236
237 let imdraw_info = imdraw::ImDrawInfo {
239 initial_buffer_size_2d: 65535 * 1024,
240 initial_buffer_size_3d: 65535 * 1024
241 };
242 let imdraw : imdraw::ImDraw<D> = imdraw::ImDraw::create(&imdraw_info).unwrap();
243
244 let mut imgui_info = imgui::ImGuiInfo::<D, A> {
246 device: &mut device,
247 swap_chain: &mut swap_chain,
248 main_window: &main_window,
249 fonts: vec![
250 imgui::FontInfo {
251 filepath: super::get_data_path("fonts/cousine_regular.ttf"),
252 glyph_ranges: None
253 },
254 imgui::FontInfo {
255 filepath: super::get_data_path("fonts/font_awesome.ttf"),
256 glyph_ranges: Some(vec![
257 [font_awesome::MINIMUM_CODEPOINT as u32, font_awesome::MAXIMUM_CODEPOINT as u32]
258 ])
259 }
260 ]
261 };
262 let imgui = imgui::ImGui::create(&mut imgui_info)?;
263
264 let mut pmfx = pmfx::Pmfx::<D>::create(&mut device, info.shader_heap_size);
266
267 pmfx.load(super::get_data_path("shaders/imdraw").as_str())?;
269 pmfx.create_render_pipeline(&device, "imdraw_blit", swap_chain.get_backbuffer_pass())?;
270
271 let size = main_window.get_size();
272 pmfx.update_window(&mut device, (size.x as f32, size.y as f32), "main_window")?;
273
274 let unit_quad_mesh = primitives::create_unit_quad_mesh(&mut device);
276
277 let cmd_buf = device.create_cmd_buf(info.num_buffers);
279
280 let mut client = Client {
282 app,
283 device,
284 main_window,
285 swap_chain,
286 cmd_buf,
287 pmfx,
288 imdraw,
289 imgui,
290 unit_quad_mesh,
291 user_config: user_config.clone(),
292 plugins: Vec::new(),
293 libs: HashMap::new(),
294 time: Time::new(),
295 delta_history: VecDeque::new(),
296 instance_name: info.name,
297 status_bar_height: STATUS_BAR_HEIGHT
298 };
299
300 if let Some(plugin_info) = &user_config.plugins {
302 for (name, info) in plugin_info {
303 client.add_plugin_lib(name, &info.path)
304 }
305 }
306
307 Ok(client)
308 }
309
310 fn update_time(&mut self) {
311 let prev_frame_start = self.time.frame_start;
313 self.time.frame_start = SystemTime::now();
314 let elapsed = prev_frame_start.elapsed();
315 if let Ok(elapsed) = elapsed {
316 self.time.raw_delta = elapsed.as_secs_f32();
318
319 if !self.time.delta_paused {
321 self.time.delta = elapsed.as_secs_f32() * self.time.time_scale;
322
323 self.time.accumulated += self.time.delta;
325
326 self.delta_history.push_front(self.time.delta);
328 if self.delta_history.len() > SMOOTH_DELTA_FRAMES {
329 self.delta_history.pop_back();
330 }
331
332 let sum : f32 = self.delta_history.iter().sum();
334 self.time.smooth_delta = sum / self.delta_history.len() as f32;
335 }
336 else {
337 self.time.delta = 0.0;
339 self.time.accumulated = 0.0;
340 self.time.smooth_delta = 0.0;
341 self.delta_history.clear();
342 }
343 }
344 }
345
346 pub fn new_frame(&mut self) -> Result<(), super::Error> {
348 self.update_time();
349
350 self.main_window.update(&mut self.app);
352 self.swap_chain.update::<A>(&mut self.device, &self.main_window, &mut self.cmd_buf);
353
354 self.cmd_buf.reset(&self.swap_chain);
356
357 self.imgui.new_frame(&mut self.app, &mut self.main_window, &mut self.device);
359 self.imgui.add_main_dock(self.status_bar_height);
360 self.status_bar_height = self.imgui.add_status_bar(self.status_bar_height);
361
362 let dock_input = self.imgui.main_dock_hovered();
364 self.app.set_input_enabled(
365 !self.imgui.want_capture_keyboard() || dock_input,
366 !self.imgui.want_capture_mouse() || dock_input);
367
368 let size = self.main_window.get_size();
369 self.pmfx.update_window(&mut self.device, (size.x as f32, size.y as f32), "main_window")?;
370
371 let size = self.imgui.get_main_dock_size();
372 self.pmfx.update_window(&mut self.device, size, "main_dock")?;
373
374 self.pmfx.new_frame(&mut self.device, &self.swap_chain)?;
376
377 self.update_user_config_windows();
379
380 Ok(())
381 }
382
383 fn save_user_config(&mut self) {
385 let user_config_file_text = serde_json::to_string_pretty(&self.user_config).unwrap();
386 let user_config_path = super::get_data_path("../user_config.json");
387 std::fs::File::create(&user_config_path).unwrap();
388 std::fs::write(&user_config_path, user_config_file_text).unwrap();
389 }
390
391 fn save_configs_to_location(&mut self, path: &str) {
393 let user_config_file_text = serde_json::to_string_pretty(&self.user_config).unwrap();
394 let user_config_path = format!("{}/user_config.json", path);
395 std::fs::File::create(&user_config_path).unwrap();
396 std::fs::write(&user_config_path, user_config_file_text).unwrap();
397 }
398
399 fn update_user_config_windows(&mut self) {
401 let mut invalidated = false;
403
404 let current = self.main_window.get_window_rect();
406 if current.x > 0 && current.y > 0 && self.user_config.main_window_rect != current {
407 self.user_config.main_window_rect = self.main_window.get_window_rect();
408 invalidated = true;
409 }
410
411 if let Some(console_window_rect) = self.user_config.console_window_rect {
413 let current = self.app.get_console_window_rect();
414 if current.x > 0 && current.y > 0 && console_window_rect != current {
415 self.user_config.console_window_rect = Some(self.app.get_console_window_rect());
416 invalidated = true;
417 }
418 }
419 else {
420 let current = self.app.get_console_window_rect();
421 if current.x > 0 && current.y > 0 {
422 self.user_config.console_window_rect = Some(self.app.get_console_window_rect());
423 invalidated = true;
424 }
425 }
426
427 if invalidated {
429 self.save_user_config();
430 }
431 }
432
433 pub fn present(&mut self, blit_view_name: &str) {
435 self.pmfx.execute(&mut self.device);
437
438 self.cmd_buf.transition_barrier(&gfx::TransitionBarrier {
440 texture: Some(self.swap_chain.get_backbuffer_texture()),
441 buffer: None,
442 state_before: gfx::ResourceState::Present,
443 state_after: gfx::ResourceState::RenderTarget,
444 });
445
446 self.cmd_buf.begin_render_pass(self.swap_chain.get_backbuffer_pass_mut());
448 self.cmd_buf.end_render_pass();
449
450 self.cmd_buf.begin_render_pass(self.swap_chain.get_backbuffer_pass_no_clear());
452
453 if let Some(tex) = self.pmfx.get_texture(blit_view_name) {
455 let vp_rect = self.main_window.get_viewport_rect();
457 self.cmd_buf.begin_event(0xff65cf82, "blit_pmfx");
458 self.cmd_buf.set_viewport(&gfx::Viewport::from(vp_rect));
459 self.cmd_buf.set_scissor_rect(&gfx::ScissorRect::from(vp_rect));
460 let srv = tex.get_srv_index().unwrap();
461 let fmt = self.swap_chain.get_backbuffer_pass_mut().get_format_hash();
462
463 let pipeline = self.pmfx.get_render_pipeline_for_format("imdraw_blit", fmt).unwrap();
464 let heap = self.device.get_shader_heap();
465
466 self.cmd_buf.set_render_pipeline(pipeline);
467 self.cmd_buf.push_render_constants(0, 2, 0, &[vp_rect.width as f32, vp_rect.height as f32]);
468
469 self.cmd_buf.set_binding(pipeline, heap, 1, srv);
470
471 self.cmd_buf.set_index_buffer(&self.unit_quad_mesh.ib);
472 self.cmd_buf.set_vertex_buffer(&self.unit_quad_mesh.vb, 0);
473 self.cmd_buf.draw_indexed_instanced(6, 1, 0, 0, 0);
474 self.cmd_buf.end_event();
475 }
476
477 let image_heaps = vec![
478 &self.pmfx.shader_heap
479 ];
480
481 self.cmd_buf.begin_event(0xff1fb6c4, "imgui");
483
484 self.imgui.render(
485 &mut self.app,
486 &mut self.main_window,
487 &mut self.device,
488 &mut self.cmd_buf,
489 &image_heaps
490 );
491
492 self.cmd_buf.end_event();
493
494 self.cmd_buf.end_render_pass();
495
496 self.cmd_buf.transition_barrier(&gfx::TransitionBarrier {
498 texture: Some(self.swap_chain.get_backbuffer_texture()),
499 buffer: None,
500 state_before: gfx::ResourceState::RenderTarget,
501 state_after: gfx::ResourceState::Present,
502 });
503 self.cmd_buf.close().unwrap();
504
505 self.device.execute(&self.cmd_buf);
507 self.swap_chain.swap(&self.device);
508 }
509
510 pub fn add_plugin_lib(&mut self, name: &str, path: &str) {
514 let abs_path = if path == "/plugins" {
515 super::get_data_path("../../plugins")
516 }
517 else {
518 String::from(path)
519 };
520
521 let lib_path = PathBuf::from(abs_path.to_string())
522 .join("target")
523 .join(crate::get_config_name())
524 .to_str().unwrap().to_string();
525
526 let src_path = PathBuf::from(abs_path.to_string())
527 .join(name)
528 .join("src")
529 .join("lib.rs")
530 .to_str().unwrap().to_string();
531
532 let plugin = PluginReloadResponder {
533 name: name.to_string(),
534 path: abs_path.to_string(),
535 output_filepath: lib_path.to_string(),
536 files: vec![
537 src_path
538 ],
539 };
540
541 if !std::path::Path::new(&lib_path).join(name.to_string() + ".dll").exists() {
542 println!("hotline_rs::client:: plugin not found: {}/{}", lib_path, name);
543 return;
544 }
545
546 println!("hotline_rs::client:: loading plugin: {}/{}", lib_path, name);
547 let lib = hot_lib_reloader::LibReloader::new(&lib_path, name, None).unwrap();
548 unsafe {
549 let create = lib.get_symbol::<unsafe extern fn() -> *mut core::ffi::c_void>("create".as_bytes());
551
552 let instance = if let Ok(create) = create {
553 create()
555 }
556 else {
557 std::ptr::null_mut()
559 };
560
561 self.plugins.push( PluginCollection {
563 name: name.to_string(),
564 instance,
565 reloader: Reloader::create(Box::new(plugin)),
566 state: PluginState::Setup
567 });
568 self.libs.insert(name.to_string(), lib);
569 }
570
571 if self.user_config.plugins.is_none() {
573 self.user_config.plugins = Some(HashMap::new());
574 }
575
576 let hotline_path = super::get_data_path("../..").replace('\\', "/");
578 let path = abs_path.replace(&hotline_path, "").replace('\\', "/");
579
580 if let Some(plugin_info) = &mut self.user_config.plugins {
581 if plugin_info.contains_key(name) {
582 plugin_info.remove(name);
583 }
584 plugin_info.insert(name.to_string(), PluginInfo { path });
585 }
586 }
587
588 fn core_ui(&mut self) {
591 if self.imgui.begin_main_menu_bar() {
593
594 if self.imgui.begin_menu("File") {
595 if self.imgui.menu_item("Open") {
597 let file = A::open_file_dialog(os::OpenFileDialogFlags::FILES, vec![".toml"]);
598 if let Ok(file) = file {
599 if !file.is_empty() {
600 let plugin_path = PathBuf::from(file[0].to_string());
602 let plugin_name = plugin_path.parent().unwrap().file_name().unwrap();
603 let plugin_path = plugin_path.parent().unwrap().parent().unwrap();
604 self.add_plugin_lib(plugin_name.to_str().unwrap(), plugin_path.to_str().unwrap());
605 }
606 }
607 }
608
609 if self.imgui.menu_item("Save User Config") {
611 let folder = A::open_file_dialog(os::OpenFileDialogFlags::FOLDERS, Vec::new());
612 if let Ok(folder) = folder {
613 if !folder.is_empty() {
614 self.save_configs_to_location(&folder[0]);
615 self.imgui.save_ini_settings_to_location(&folder[0]);
616 }
617 }
618 }
619
620 self.imgui.separator();
621 if self.imgui.menu_item("Exit") {
622 self.app.exit(0);
623 }
624
625 self.imgui.end_menu();
626 }
627
628 if self.imgui.begin_menu("Plugin") {
630 for plugin in &mut self.plugins {
631 if self.imgui.begin_menu(&plugin.name) {
632 if self.imgui.menu_item("Reload") {
633 plugin.state = PluginState::Setup;
634 }
635 if self.imgui.menu_item("Unload") {
636 plugin.state = PluginState::Unload;
637 }
638 self.imgui.end_menu();
639 }
640 }
641 self.imgui.end_menu();
642 }
643
644 if self.imgui.begin_menu("Time") {
645 let pause_text = if self.time.delta_paused {
647 "Resume Delta Time"
648 }
649 else {
650 "Pause Delta Time"
651 };
652
653 if self.imgui.menu_item(pause_text) {
654 self.time.delta_paused = !self.time.delta_paused;
655 }
656
657 let pause_text = if self.time.paused {
659 "Resume Updates"
660 }
661 else {
662 "Pause Updates"
663 };
664
665 if self.imgui.menu_item(pause_text) {
666 self.time.paused = !self.time.paused;
667 }
668
669 if let Some(mut step) = self.time.fixed_delta {
671 let mut fixed = true;
672 if self.imgui.checkbox("##", &mut fixed) && !fixed {
673 self.time.fixed_delta = None;
674 }
675 self.imgui.same_line();
676 self.imgui.dummy(5.0, 0.0);
677 self.imgui.same_line();
678 if self.imgui.input_float("Fixed Timestep", &mut step) {
679 self.time.fixed_delta = Some(step)
680 }
681 }
682 else {
683 let mut fixed = false;
684 if self.imgui.checkbox("Fixed Timestep", &mut fixed) && fixed {
685 self.time.fixed_delta = Some(1.0 / 60.0);
686 }
687 }
688
689 self.imgui.input_float("Time Scale", &mut self.time.time_scale);
691
692 self.imgui.end_menu();
693 }
694
695 self.imgui.end_main_menu_bar();
696 }
697 if self.imgui.begin_window("status_bar") {
699 let fps = maths_rs::round(1.0 / self.time.raw_delta) as u32;
701 let cpu_ms = self.time.raw_delta * 1000.0;
702 let gpu_ms = self.pmfx.get_total_stats().gpu_time_ms;
703 self.imgui.text(
704 &format!("fps: {} | cpu: {:.2}(ms) | gpu: {:.2}(ms)",
705 fps, cpu_ms, gpu_ms
706 ));
707 self.imgui.same_line();
708
709 let mut hot_name = String::from("");
711 let mut col = vec4f(1.0, 1.0, 1.0, 1.0);
712 for plugin in &self.plugins {
713 if plugin.reloader.is_hot() {
714 if !hot_name.is_empty() {
715 hot_name += " | "
716 }
717 hot_name = plugin.name.to_string();
718 col = vec4f(1.0, 0.0, 0.0, 1.0);
719 }
720 }
721
722 if self.pmfx.reloader.is_hot() {
724 if !hot_name.is_empty() {
725 hot_name += " | "
726 }
727 hot_name += "pmfx";
728 col = vec4f(1.0, 0.0, 0.0, 1.0);
729 }
730
731 let hot_text = format!("{} {}", hot_name, font_awesome::strs::FIRE);
732 self.imgui.right_align(self.imgui.calc_text_size(&hot_text).0 + 10.0);
733 self.imgui.colour_text(&hot_text, col);
734 }
735 self.imgui.end();
736 }
737
738 fn update_plugins(mut self) -> Self {
740 let mut plugins = std::mem::take(&mut self.plugins);
742
743 for plugin in &mut plugins {
745 let lib = self.libs.get(&plugin.name).expect("hotline::client: lib missing for plugin");
746 unsafe {
747 let ui = lib.get_symbol::<unsafe extern fn(Self, *mut core::ffi::c_void, *mut core::ffi::c_void) -> Self>("ui".as_bytes());
748 if let Ok(ui_fn) = ui {
749 let imgui_ctx = self.imgui.get_current_context();
750 self = ui_fn(self, plugin.instance, imgui_ctx);
751 }
752 }
753 }
754
755 let mut reload = false;
757 for plugin in &mut plugins {
758 if plugin.reloader.check_for_reload() == reloader::ReloadState::Available || plugin.state == PluginState::Reload {
759 self.swap_chain.wait_for_last_frame();
760 reload = true;
761 plugin.state = PluginState::Reload;
762 break;
763 }
764 }
765
766 if reload {
769 for plugin in &mut plugins {
770 if plugin.state == PluginState::None {
771 plugin.state = PluginState::Setup
772 }
773 }
774 }
775
776 for plugin in &plugins {
778 if plugin.state != PluginState::None {
779 unsafe {
780 let lib = self.libs.get(&plugin.name).expect("hotline::client: lib missing for plugin");
781 let unload = lib.get_symbol::<unsafe extern fn(Self, PluginInstance) -> Self>("unload".as_bytes());
782 if let Ok(unload_fn) = unload {
783 self = unload_fn(self, plugin.instance);
784 }
785 }
786 }
787 }
788
789 loop {
791 let mut todo = false;
792 for i in 0..plugins.len() {
793 if plugins[i].state == PluginState::Unload {
794 if let Some(plugin_info) = &mut self.user_config.plugins {
795 plugin_info.remove_entry(&plugins[i].name);
796 }
797 self.libs.remove_entry(&plugins[i].name);
798 plugins.remove(i);
799 todo = true;
800 break;
801 }
802 }
803 if !todo {
804 break;
805 }
806 }
807
808 for plugin in &mut plugins {
810 if plugin.state == PluginState::Reload {
811 let lib = self.libs.get_mut(&plugin.name).expect("hotline::client: lib missing for plugin");
813 let start = SystemTime::now();
814 loop {
815 if lib.update().unwrap() {
816 break;
817 }
818 if start.elapsed().unwrap() > std::time::Duration::from_secs(10) {
819 println!("hotline::client: [warning] reloading plugin: {} timed out", plugin.name);
820 break;
821 }
822 std::hint::spin_loop();
823 }
824
825 plugin.reloader.complete_reload();
827
828 unsafe {
830 let create = lib.get_symbol::<unsafe extern fn() -> *mut core::ffi::c_void>("create".as_bytes());
831 if let Ok(create_fn) = create {
832 plugin.instance = create_fn();
833 }
834 }
835 plugin.state = PluginState::Setup;
837 }
838 }
839
840 for plugin in &plugins {
842 let lib = self.libs.get(&plugin.name).expect("hotline::client: lib missing for plugin");
843 unsafe {
844 if plugin.state == PluginState::Setup {
845 let setup = lib.get_symbol::<unsafe extern fn(Self, *mut core::ffi::c_void) -> Self>("setup".as_bytes());
846 if let Ok(setup_fn) = setup {
847 self = setup_fn(self, plugin.instance);
848 }
849 }
850 }
851 }
852
853 if !self.time.paused {
855 for plugin in &mut plugins {
856 let lib = self.libs.get(&plugin.name).expect("hotline::client: lib missing for plugin");
857 unsafe {
858 let update = lib.get_symbol::<unsafe extern fn(Self, *mut core::ffi::c_void) -> Self>("update".as_bytes());
859 if let Ok(update_fn) = update {
860 self = update_fn(self, plugin.instance);
861 }
862 }
863 plugin.state = PluginState::None;
864 }
865 }
866
867 self.plugins = plugins;
869 self
870 }
871
872 fn unload(mut self) {
874 let plugins = std::mem::take(&mut self.plugins);
875 for plugin in &plugins {
876 unsafe {
877 let lib = self.libs.get(&plugin.name).expect("hotline::client: lib missing for plugin");
878 let unload = lib.get_symbol::<unsafe extern fn(Self, PluginInstance) -> Self>("unload".as_bytes());
879 if let Ok(unload_fn) = unload {
880 self = unload_fn(self, plugin.instance);
881 }
882 }
883 }
884 }
885
886 pub fn serialise_plugin_data<T: Serialize>(&mut self, plugin_name: &str, data: &T) {
897 let serialised = serde_json::to_value(data).unwrap();
898 if self.user_config.plugin_data.is_none() {
899 self.user_config.plugin_data = Some(HashMap::new());
900 }
901
902 if let Some(plugin_data) = &mut self.user_config.plugin_data {
903 *plugin_data.entry(plugin_name.to_string()).or_insert(serde_json::Value::default()) = serialised;
904 }
905 }
906
907 pub fn deserialise_plugin_data<T: Default + serde::de::DeserializeOwned>(&mut self, plugin_name: &str) -> T {
909 if let Some(plugin_data) = &self.user_config.plugin_data {
911 if plugin_data.contains_key(plugin_name) {
912 serde_json::from_value(plugin_data[plugin_name].clone()).unwrap()
913 }
914 else {
915 T::default()
916 }
917 }
918 else {
919 T::default()
920 }
921 }
922
923 pub fn run(mut self) -> Result<(), super::Error> {
925 while self.app.run() {
926
927 self.new_frame()?;
928
929 self.core_ui();
930 self.pmfx.show_ui(&mut self.imgui, true);
931
932 self = self.update_plugins();
933
934 if let Some(tex) = self.pmfx.get_texture("main_colour") {
935 self.imgui.image_window("main_dock", tex);
936 }
937
938 if let Ok(elapsed) = self.time.frame_start.elapsed() {
940 self.time.update_delta = elapsed.as_secs_f32();
941 }
942
943 self.present("");
944
945 let info_queue = self.device.get_info_queue_messages()?;
947 for msg in info_queue {
948 println!("{}", msg);
949 }
950
951 self.pmfx.shader_heap.cleanup_dropped_resources(&self.swap_chain);
953 self.device.cleanup_dropped_resources(&self.swap_chain);
954 }
955
956 self.save_user_config();
958 self.imgui.save_ini_settings();
959 self.swap_chain.wait_for_last_frame();
960
961 self.unload();
963
964 Ok(())
965 }
966
967 pub fn run_once(mut self) -> Result<(), super::Error> {
969 for i in 0..3 {
970 self.new_frame()?;
971
972 self.core_ui();
973 self.pmfx.show_ui(&mut self.imgui, true);
974
975 self = self.update_plugins();
976
977 if let Some(tex) = self.pmfx.get_texture("main_colour") {
978 self.imgui.image_window("main_dock", tex);
979 }
980
981 self.pmfx.execute(&mut self.device);
983
984 self.cmd_buf.transition_barrier(&gfx::TransitionBarrier {
986 texture: Some(self.swap_chain.get_backbuffer_texture()),
987 buffer: None,
988 state_before: gfx::ResourceState::Present,
989 state_after: gfx::ResourceState::RenderTarget,
990 });
991
992 self.cmd_buf.begin_render_pass(self.swap_chain.get_backbuffer_pass_mut());
994 self.cmd_buf.end_render_pass();
995
996 self.cmd_buf.begin_render_pass(self.swap_chain.get_backbuffer_pass_no_clear());
998
999 self.cmd_buf.begin_event(0xff1fb6c4, "imgui");
1001
1002 let image_heaps = vec![
1003 &self.pmfx.shader_heap
1004 ];
1005
1006 self.imgui.render(
1007 &mut self.app,
1008 &mut self.main_window,
1009 &mut self.device,
1010 &mut self.cmd_buf,
1011 &image_heaps
1012 );
1013
1014 self.cmd_buf.end_event();
1015
1016 self.cmd_buf.end_render_pass();
1017
1018 self.cmd_buf.transition_barrier(&gfx::TransitionBarrier {
1020 texture: Some(self.swap_chain.get_backbuffer_texture()),
1021 buffer: None,
1022 state_before: gfx::ResourceState::RenderTarget,
1023 state_after: gfx::ResourceState::Present,
1024 });
1025
1026 let readback_request = self.cmd_buf.read_back_backbuffer(&self.swap_chain)?;
1027
1028 self.cmd_buf.close().unwrap();
1029
1030 self.device.execute(&self.cmd_buf);
1032 self.swap_chain.swap(&self.device);
1033
1034 self.swap_chain.wait_for_last_frame();
1035
1036 if i == 2 {
1038 let data = readback_request.map(&gfx::MapInfo {
1039 subresource: 0,
1040 read_start: 0,
1041 read_end: usize::MAX
1042 })?;
1043
1044 let output_dir = "target/test_output";
1045 if !std::path::PathBuf::from(output_dir.to_string()).exists() {
1046 std::fs::create_dir(output_dir)?;
1047 }
1048
1049 let output_filepath = format!("{}/{}.png", output_dir, &self.instance_name);
1050 image::write_to_file_from_gpu(&output_filepath, &data)?;
1051
1052 readback_request.unmap();
1053 }
1054
1055 self.pmfx.shader_heap.cleanup_dropped_resources(&self.swap_chain);
1057 self.device.cleanup_dropped_resources(&self.swap_chain);
1058 }
1059
1060 self.swap_chain.wait_for_last_frame();
1061
1062 self.unload();
1064
1065 Ok(())
1066 }
1067}