1use std::sync::{Arc, Mutex};
3
4pub use eframe;
5use eframe::egui_wgpu::{Callback, CallbackResources, CallbackTrait};
6pub use eframe::{egui, wgpu};
7use graphics::RenderData;
8pub use image;
9pub use nalgebra;
10
11pub mod graphics;
12pub mod mipmapping;
13#[cfg(target_arch = "wasm32")]
14pub mod web;
15
16pub trait PeterEngineApp: Send + 'static {
17 const WINDOW_TITLE: &'static str;
18
19 type RenderResources: Send + Sync + 'static;
20
21 fn get_shader_source() -> String;
22 fn init(
23 &mut self,
24 cc: &eframe::CreationContext,
25 render_data: &mut RenderData,
26 ) -> Self::RenderResources;
27 fn update(&mut self, egui_ctx: &egui::Context, frame: &mut eframe::Frame, dt: f32);
28 fn central_panel_input(
29 &mut self,
30 _egui_ctx: &egui::Context,
31 _response: egui::Response,
32 _allocated_rect: &egui::Rect,
33 ) {
34 }
35 fn prepare(
36 &mut self,
37 render_data: &mut RenderData,
38 resources: &mut Self::RenderResources,
39 device: &wgpu::Device,
40 queue: &wgpu::Queue,
41 encoder: &mut wgpu::CommandEncoder,
42 screen_size: (u32, u32),
43 ) -> Vec<wgpu::CommandBuffer>;
44 fn paint<'rp>(
45 &mut self,
46 render_data: &'rp RenderData,
47 resources: &'rp Self::RenderResources,
48 info: eframe::epaint::PaintCallbackInfo,
49 render_pass: &mut wgpu::RenderPass<'rp>,
50 );
51}
52
53pub struct EframeApp<GameState> {
54 locked_state: Arc<Mutex<GameState>>,
55}
56
57impl<GameState: PeterEngineApp> EframeApp<GameState> {
58 pub fn new(mut game_state: GameState, cc: &eframe::CreationContext) -> Self {
59 let wgpu_render_state = cc.wgpu_render_state.as_ref().unwrap();
60 let mut w = wgpu_render_state.renderer.write();
61 let shader_source = GameState::get_shader_source();
62 let mut rd = RenderData::new(cc, &shader_source);
63 let resources = game_state.init(cc, &mut rd);
64 let locked_state = Arc::new(Mutex::new(game_state));
65 w.callback_resources.insert((rd, resources));
66 Self { locked_state }
67 }
68}
69
70struct PaintCallback<GameState> {
71 pixel_perfect_size: (u32, u32),
72 locked_state: Arc<Mutex<GameState>>,
73}
74
75impl<GameState: PeterEngineApp> CallbackTrait for PaintCallback<GameState> {
76 fn prepare(
77 &self,
78 device: &wgpu::Device,
79 queue: &wgpu::Queue,
80 encoder: &mut wgpu::CommandEncoder,
81 callback_resources: &mut CallbackResources,
82 ) -> Vec<wgpu::CommandBuffer> {
83 let (render_data, resources) =
84 callback_resources.get_mut::<(RenderData, GameState::RenderResources)>().unwrap();
85 render_data.pixel_perfect_size = self.pixel_perfect_size;
86 self.locked_state.lock().unwrap().prepare(
87 render_data,
88 resources,
89 device,
90 queue,
91 encoder,
92 self.pixel_perfect_size,
93 )
94 }
95
96 fn paint<'rp>(
97 &self,
98 info: eframe::epaint::PaintCallbackInfo,
99 render_pass: &mut wgpu::RenderPass<'rp>,
100 callback_resources: &'rp CallbackResources,
101 ) {
102 let (render_data, resources) =
103 callback_resources.get::<(RenderData, GameState::RenderResources)>().unwrap();
104 self.locked_state.lock().unwrap().paint(render_data, resources, info, render_pass)
105 }
106}
107
108impl<GameState: PeterEngineApp> eframe::App for EframeApp<GameState> {
109 fn update(&mut self, egui_ctx: &egui::Context, frame: &mut eframe::Frame) {
110 let dt = egui_ctx.input(|inp| inp.stable_dt.clamp(0.0, 0.15));
111 {
112 let mut guard = self.locked_state.lock().unwrap();
113 guard.update(egui_ctx, frame, dt);
114 }
115
116 egui::CentralPanel::default().frame(egui::Frame::none().fill(egui::Color32::BLACK)).show(
117 egui_ctx,
118 |ui| {
119 let (id, allocated_rect) = ui.allocate_space(ui.available_size());
120 let response = ui.interact(allocated_rect, id, egui::Sense::click_and_drag());
121 self
122 .locked_state
123 .lock()
124 .unwrap()
125 .central_panel_input(&egui_ctx, response, &allocated_rect);
126
127 let painter = ui.painter();
128 let pixel_perfect_rect = egui::Rect::from_two_pos(
130 painter.round_pos_to_pixels(allocated_rect.left_top()),
131 painter.round_pos_to_pixels(allocated_rect.right_bottom()),
132 );
133 let pixels_per_point = egui_ctx.pixels_per_point();
134 let pixel_perfect_size = (
136 ((pixel_perfect_rect.width() * pixels_per_point).round() as u32).max(1),
137 ((pixel_perfect_rect.height() * pixels_per_point).round() as u32).max(1),
138 );
139
140 painter.add(Callback::new_paint_callback(pixel_perfect_rect, PaintCallback {
141 pixel_perfect_size,
142 locked_state: Arc::clone(&self.locked_state),
143 }));
144 },
145 );
146 }
147}
148
149#[cfg(not(target_arch = "wasm32"))]
150pub fn launch<GameState: PeterEngineApp>(game_state: GameState) -> Result<(), eframe::Error> {
151 let mut native_options = eframe::NativeOptions::default();
152 native_options.depth_buffer = 32;
153 native_options.multisampling = crate::graphics::MSAA_COUNT as u16;
154 eframe::run_native(
155 GameState::WINDOW_TITLE,
156 native_options,
157 Box::new(move |cc| Box::new(EframeApp::new(game_state, cc))),
158 )
159}