bottomless_pit/
engine_handle.rs

1//! Contains both the Engine and the Engine builder
2//! Both of these are crucial to using the engine as the
3//! builder lets you customize the engine at the start, and the
4//! Engine gives you access to all the crucial logic functions
5
6use glyphon::{Attrs, Metrics, Shaping};
7
8use image::ImageError;
9use spin_sleep::SpinSleeper;
10use std::num::NonZeroU64;
11use std::path::Path;
12use web_time::Instant;
13use wgpu::{CreateSurfaceError, RequestDeviceError};
14use winit::application::ApplicationHandler;
15use winit::error::OsError;
16use winit::event::*;
17use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy};
18use winit::window::BadIcon;
19
20use crate::context::{GraphicsContext, WindowOptions};
21use crate::input::{InputHandle, Key, ModifierKeys, MouseKey};
22use crate::render::render;
23use crate::resource;
24use crate::resource::{
25    InProgressResource, Loader, LoadingOp, Resource, ResourceError, ResourceId, ResourceManager,
26    ResourceType,
27};
28use crate::shader::{FinalShaderOptions, IntermediateOptions, Shader};
29use crate::text::Font;
30use crate::texture::{SamplerType, Texture};
31use crate::vectors::Vec2;
32use crate::Game;
33
34/// The thing that makes the computer go
35pub struct Engine {
36    input_handle: InputHandle,
37    event_loop: Option<EventLoop<BpEvent>>,
38    window_options: Option<WindowOptions>,
39    proxy: EventLoopProxy<BpEvent>,
40    cursor_visibility: bool,
41    should_close: bool,
42    close_key: Option<Key>,
43    target_fps: Option<u16>,
44    last_frame: Instant,
45    spin_sleeper: SpinSleeper,
46    current_frametime: Instant,
47    size: Vec2<u32>,
48    pub(crate) context: Option<GraphicsContext>,
49    pub(crate) resource_manager: ResourceManager,
50    pub(crate) loader: Loader,
51    pub(crate) defualt_resources: DefualtResources,
52    ma_frame_time: f32,
53}
54
55impl Engine {
56    fn new(builder: EngineBuilder) -> Result<Self, BuildError> {
57        cfg_if::cfg_if! {
58            if #[cfg(target_arch = "wasm32")] {
59                std::panic::set_hook(Box::new(console_error_panic_hook::hook));
60                console_log::init_with_level(log::Level::Info).expect("Couldn't initialize logger");
61            } else {
62                env_logger::init();
63            }
64        }
65
66        let cursor_visibility = true;
67        let input_handle = InputHandle::new();
68        let size: Vec2<u32> = builder.resolution.into();
69        let target_fps = builder.target_fps;
70
71        let event_loop: EventLoop<BpEvent> = EventLoop::with_user_event().build().unwrap();
72        let proxy = event_loop.create_proxy();
73
74        let resource_manager = ResourceManager::new();
75
76        let close_key = builder.close_key;
77
78        let line_id = resource::generate_id::<Shader>();
79        let generic_id = resource::generate_id::<Shader>();
80        let white_pixel_id = resource::generate_id::<Texture>();
81
82        let defualt_resources = DefualtResources {
83            default_pipeline_id: generic_id,
84            defualt_texture_id: white_pixel_id,
85            line_pipeline_id: line_id,
86        };
87
88        Ok(Self {
89            input_handle,
90            event_loop: Some(event_loop),
91            window_options: Some(builder.into()),
92            proxy,
93            cursor_visibility,
94            should_close: false,
95            close_key,
96            target_fps,
97            last_frame: Instant::now(),
98            current_frametime: Instant::now(),
99            spin_sleeper: SpinSleeper::default(),
100            size,
101            context: None,
102            resource_manager,
103            loader: Loader::new(),
104            defualt_resources,
105            ma_frame_time: 0.0,
106        })
107    }
108
109    /// Checks if a key is down
110    pub fn is_key_down(&self, key: Key) -> bool {
111        self.input_handle.is_key_down(key)
112    }
113
114    /// Checks if a key is up
115    pub fn is_key_up(&self, key: Key) -> bool {
116        self.input_handle.is_key_up(key)
117    }
118
119    /// Only returns `true` on the frame where the key is first pressed down
120    pub fn is_key_pressed(&self, key: Key) -> bool {
121        self.input_handle.is_key_pressed(key)
122    }
123
124    /// Only returns `true` on the frame where the key first returns back up
125    pub fn is_key_released(&self, key: Key) -> bool {
126        self.input_handle.is_key_released(key)
127    }
128
129    /// This will tell you if certain keys like CTRL or Shift
130    /// are modifying the current key presses.
131    pub fn check_modifiers(&self, modifer: ModifierKeys) -> bool {
132        self.input_handle.check_modifiers(modifer)
133    }
134
135    /// returns the text vule of any keys held down helpfull for text
136    /// entry. As if Shift + w is held this will return `Some("W")`
137    pub fn get_current_text(&self) -> Option<&str> {
138        self.input_handle.get_text_value()
139    }
140
141    /// Checks if a mouse key is down
142    pub fn is_mouse_key_down(&self, key: MouseKey) -> bool {
143        self.input_handle.is_mouse_key_down(key)
144    }
145
146    /// Checks if a mouse key is up
147    pub fn is_mouse_key_up(&self, key: MouseKey) -> bool {
148        self.input_handle.is_mouse_key_up(key)
149    }
150
151    /// Only returns `true` on the frame where the key is first pressed down
152    pub fn is_mouse_key_pressed(&self, key: MouseKey) -> bool {
153        self.input_handle.is_mouse_key_pressed(key)
154    }
155
156    /// Only returns `true` on the frame where the key first returns back up
157    pub fn is_mouse_key_released(&self, key: MouseKey) -> bool {
158        self.input_handle.is_mouse_key_released(key)
159    }
160
161    /// Gives the current position of the mouse in physical pixels
162    pub fn get_mouse_position(&self) -> Vec2<f32> {
163        self.input_handle.get_mouse_position()
164    }
165
166    /// Returns how much the mouse has moved in the last frame
167    pub fn get_mouse_delta(&self) -> Vec2<f32> {
168        self.input_handle.get_mouse_delta()
169    }
170
171    /// Checks if the window has focus
172    /// # Panics
173    /// When called outside of the functions in the [Game] trait
174    pub fn window_has_focus(&self) -> bool {
175        let context = self
176            .context
177            .as_ref()
178            .expect("Context hasnt been created yet run inside impl Game");
179        context.window.has_focus()
180    }
181
182    /// Checks if the window is maximized not fullscreened
183    /// # Panics
184    /// When called outside of the functions in the [Game] trait
185    pub fn is_window_maximized(&self) -> bool {
186        let context = self
187            .context
188            .as_ref()
189            .expect("Context hasnt been created yet run inside impl Game");
190        context.window.is_maximized()
191    }
192
193    /// Checks to see if the window is minimized
194    /// # Panics
195    /// When called outside of the functions in the [Game] trait
196    pub fn is_window_minimized(&self) -> bool {
197        let context = self
198            .context
199            .as_ref()
200            .expect("Context hasnt been created yet run inside impl Game");
201        context.window.is_minimized().unwrap_or(false)
202    }
203
204    /// Checks to see if the window is fullscreen not maximized
205    /// # Panics
206    /// When called outside of the functions in the [Game] trait
207    pub fn is_window_fullscreen(&self) -> bool {
208        // based on limited docs knowledge this should work
209        let context = self
210            .context
211            .as_ref()
212            .expect("Context hasnt been created yet run inside impl Game");
213        context.window.fullscreen().is_some()
214    }
215
216    /// Will maximize the window
217    /// # Panics
218    /// When called outside of the functions in the [Game] trait
219    pub fn maximize_window(&self) {
220        let context = self
221            .context
222            .as_ref()
223            .expect("Context hasnt been created yet run inside impl Game");
224        context.window.set_maximized(true);
225    }
226
227    /// Will minimize the window
228    /// # Panics
229    /// When called outside of the functions in the [Game] trait
230    pub fn minimize_window(&self) {
231        let context = self
232            .context
233            .as_ref()
234            .expect("Context hasnt been created yet run inside impl Game");
235        context.window.set_minimized(true);
236    }
237
238    /// Will close the window and stop the program
239    pub fn close(&mut self) {
240        self.should_close = true;
241    }
242
243    /// Will attempt to set the window icon for more details check the
244    /// [winit docs](https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_window_icon)
245    /// # Panics
246    /// When called outside of the functions in the [Game] trait
247    pub fn set_window_icon(&self, path: &str) -> Result<(), IconError> {
248        let image = image::open(path)?.into_rgba8();
249        let (width, height) = image.dimensions();
250        let image_bytes = image.into_raw();
251        let icon = winit::window::Icon::from_rgba(image_bytes, width, height)?;
252
253        let context = self
254            .context
255            .as_ref()
256            .expect("Context hasnt been created yet run inside impl Game");
257
258        context.window.set_window_icon(Some(icon));
259        Ok(())
260    }
261
262    /// Sets the window title
263    /// # Panics
264    /// When called outside of the functions in the [Game] trait
265    pub fn set_window_title(&self, title: &str) {
266        let context = self
267            .context
268            .as_ref()
269            .expect("Context hasnt been created yet run inside impl Game");
270        context.window.set_title(title);
271    }
272
273    /// Changes the Position of the window in Physical Pixles
274    /// # Panics
275    /// When called outside of the functions in the [Game] trait
276    pub fn set_window_position(&self, x: f32, y: f32) {
277        let context = self
278            .context
279            .as_ref()
280            .expect("Context hasnt been created yet run inside impl Game");
281        context
282            .window
283            .set_outer_position(winit::dpi::PhysicalPosition::new(x, y));
284    }
285
286    /// Sets the physical minimum size of the window
287    /// # Panics
288    /// When called outside of the functions in the [Game] trait
289    pub fn set_window_min_size(&self, width: f32, height: f32) {
290        let context = self
291            .context
292            .as_ref()
293            .expect("Context hasnt been created yet run inside impl Game");
294        context
295            .window
296            .set_min_inner_size(Some(winit::dpi::PhysicalSize::new(width, height)));
297    }
298
299    /// Gets the physical postion of the window
300    /// # Panics
301    /// When called outside of the functions in the [Game] trait
302    pub fn get_window_position(&self) -> Option<Vec2<i32>> {
303        let context = self
304            .context
305            .as_ref()
306            .expect("Context hasnt been created yet run inside impl Game");
307        match context.window.outer_position() {
308            Ok(v) => Some((v.x, v.y).into()),
309            Err(_) => None,
310        }
311    }
312
313    /// Gets the phyisical size of the window,
314    pub fn get_window_size(&self) -> Vec2<u32> {
315        self.size
316    }
317
318    /// Gets the scale factor to help handle diffrence between phyiscial and logical pixels
319    /// # Panics
320    /// When called outside of the functions in the [Game] trait
321    pub fn get_window_scale_factor(&self) -> f64 {
322        let context = self
323            .context
324            .as_ref()
325            .expect("Context hasnt been created yet run inside impl Game");
326        context.window.scale_factor()
327    }
328
329    /// Toggels fullscreen mode may fail on certain Operating Systems
330    /// check the [winit docs](https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_fullscreen)
331    /// for more information
332    /// # Panics
333    /// When called outside of the functions in the [Game] trait
334    pub fn toggle_fullscreen(&self) {
335        let context = self
336            .context
337            .as_ref()
338            .expect("Context hasnt been created yet run inside impl Game");
339        if self.is_window_fullscreen() {
340            context
341                .window
342                .set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
343        } else {
344            context.window.set_fullscreen(None);
345        }
346    }
347
348    /// Hides the cursor
349    /// # Panics
350    /// When called outside of the functions in the [Game] trait
351    pub fn hide_cursor(&mut self) {
352        let context = self
353            .context
354            .as_ref()
355            .expect("Context hasnt been created yet run inside impl Game");
356        context.window.set_cursor_visible(false);
357        self.cursor_visibility = false;
358    }
359
360    /// Shows the cursor if its hidden
361    /// # Panics
362    /// When called outside of the functions in the [Game] trait
363    pub fn show_cursor(&mut self) {
364        let context = self
365            .context
366            .as_ref()
367            .expect("Context hasnt been created yet run inside impl Game");
368        context.window.set_cursor_visible(true);
369        self.cursor_visibility = true;
370    }
371
372    /// Gets the time since the previous frame or change in time between now and last frame
373    pub fn get_frame_delta_time(&self) -> f32 {
374        Instant::now().duration_since(self.last_frame).as_secs_f32()
375    }
376
377    /// Returns a moving average of the last two frame times
378    pub fn get_stable_fps(&self) -> f32 {
379        1.0 / self.ma_frame_time
380    }
381
382    /// Gets the current target fps
383    pub fn get_target_fps(&self) -> Option<u16> {
384        self.target_fps
385    }
386
387    /// Will uncap the framerate and cause the engine to redner and update as soon
388    /// as the next frame is ready unless VSYNC is on then it will draw at the
389    /// VYSNC rate, which is dependant on user hardware.
390    pub fn remove_target_fps(&mut self) {
391        self.target_fps = None;
392    }
393
394    /// Will turn off vysnc if the platform suports it, using
395    /// AutoNoVsync for more information check
396    /// [PresentMode::AutoNoVsync](https://docs.rs/wgpu/latest/wgpu/enum.PresentMode.html).
397    /// # Panics
398    /// When called outside of the functions in the [Game] trait
399    pub fn remove_vsync(&mut self) {
400        let context = self
401            .context
402            .as_mut()
403            .expect("Context hasnt been created yet run inside impl Game");
404        context.config.present_mode = wgpu::PresentMode::AutoNoVsync;
405        context
406            .surface
407            .configure(&context.wgpu.device, &context.config);
408    }
409
410    /// Will turn off vysnc if the platform suports it, using
411    /// AutoVsync for more information check
412    /// [PresentMode::AutoVsync](https://docs.rs/wgpu/latest/wgpu/enum.PresentMode.html).
413    /// # Panics
414    /// When called outside of the functions in the [Game] trait
415    pub fn add_vsync(&mut self) {
416        let context = self
417            .context
418            .as_mut()
419            .expect("Context hasnt been created yet run inside impl Game");
420        context.config.present_mode = wgpu::PresentMode::AutoVsync;
421        context
422            .surface
423            .configure(&context.wgpu.device, &context.config);
424    }
425
426    /// Sets a target fps cap. The thread will spin sleep using the
427    /// [spin_sleep](https://docs.rs/spin_sleep/latest/spin_sleep/index.html) crate
428    /// untill the desired frame time is reached. If the frames are slower than the target
429    /// no action is taken to "speed" up the rendering and updating
430    pub fn set_target_fps(&mut self, fps: u16) {
431        self.target_fps = Some(fps);
432    }
433
434    /// Measures string based on the default font. To measure a string with a custom font
435    /// use [TextMaterial::get_measurements()](../text/struct.TextMaterial.html#method.get_measurements)
436    /// # Panics
437    /// When called outside of the functions in the [Game] trait
438    pub fn measure_string(&mut self, text: &str, font_size: f32, line_height: f32) -> Vec2<f32> {
439        let size = self.get_window_size();
440        let scale_factor = self.get_window_scale_factor();
441
442        let context = self
443            .context
444            .as_mut()
445            .expect("Context hasnt been created yet run inside impl Game");
446
447        let mut buffer = glyphon::Buffer::new(
448            &mut context.text_renderer.font_system,
449            Metrics::new(font_size, line_height),
450        );
451
452        let physical_width = (size.x as f64 * scale_factor) as f32;
453        let physical_height = (size.y as f64 * scale_factor) as f32;
454
455        buffer.set_size(
456            &mut context.text_renderer.font_system,
457            Some(physical_height),
458            Some(physical_width),
459        );
460        buffer.set_text(
461            &mut context.text_renderer.font_system,
462            text,
463            Attrs::new(),
464            Shaping::Basic,
465        );
466
467        let height = buffer.lines.len() as f32 * buffer.metrics().line_height;
468        let run_width = buffer
469            .layout_runs()
470            .map(|run| run.line_w)
471            .max_by(f32::total_cmp)
472            .unwrap_or(0.0);
473
474        Vec2 {
475            x: run_width,
476            y: height,
477        }
478    }
479
480    /// Loads in a byte vector resource, can be used to load arbitary files.
481    pub fn create_resource<P: AsRef<Path>>(
482        &mut self,
483        path: P,
484        loading_op: LoadingOp,
485    ) -> ResourceId<Vec<u8>> {
486        let typed_id = resource::generate_id::<Vec<u8>>();
487        let id = typed_id.get_id();
488        let path = path.as_ref();
489        let ip_resource = InProgressResource::new(path, id, ResourceType::Bytes, loading_op);
490
491        self.loader.blocking_load(ip_resource, self.proxy.clone());
492        typed_id
493    }
494
495    /// This returns the current number of resources that
496    /// are loading
497    pub fn get_loading_resource_count(&self) -> usize {
498        self.loader.get_loading_resources()
499    }
500
501    /// Attemps to fetch a byte resource.
502    ///
503    /// Returns `None` if the resource isnt loaded yet.
504    pub fn get_byte_resource(&self, id: ResourceId<Vec<u8>>) -> Option<&Vec<u8>> {
505        self.resource_manager.get_byte_resource(&id)
506    }
507
508    pub(crate) fn get_proxy(&self) -> EventLoopProxy<BpEvent> {
509        self.proxy.clone()
510    }
511
512    pub(crate) fn get_resources(&self) -> &ResourceManager {
513        &self.resource_manager
514    }
515
516    pub(crate) fn defualt_material_bg_id(&self) -> ResourceId<Texture> {
517        self.defualt_resources.defualt_texture_id
518    }
519
520    pub(crate) fn defualt_pipe_id(&self) -> ResourceId<Shader> {
521        self.defualt_resources.default_pipeline_id
522    }
523
524    pub(crate) fn line_pipe_id(&self) -> ResourceId<Shader> {
525        self.defualt_resources.line_pipeline_id
526    }
527
528    /// Takes the struct that implements the Game trait and starts the winit event loop running the game
529    pub fn run<T>(mut self, game: T)
530    where
531        T: Game + 'static,
532    {
533        let event_loop = self.event_loop.take().unwrap(); //should never panic
534        event_loop.run_app(&mut (self, game)).unwrap();
535    }
536
537    fn update(&mut self, elwt: &ActiveEventLoop) {
538        self.last_frame = Instant::now();
539        let dt = self
540            .last_frame
541            .duration_since(self.current_frametime)
542            .as_secs_f32();
543
544        self.ma_frame_time = (self.ma_frame_time + dt) / 2.0;
545
546        if self.should_close {
547            elwt.exit();
548            return;
549        }
550
551        self.input_handle.end_of_frame_refresh();
552        if let Some(key) = self.close_key {
553            if self.input_handle.is_key_down(key) {
554                elwt.exit();
555                return;
556            }
557        }
558
559        if let Some(frame_rate) = self.target_fps {
560            let frame_time = Instant::now()
561                .duration_since(self.current_frametime)
562                .as_nanos() as u64;
563            let desired_time_between_frames = 1000000000 / frame_rate as u64;
564            if frame_time < desired_time_between_frames {
565                self.spin_sleeper
566                    .sleep_ns(desired_time_between_frames - frame_time);
567            }
568        }
569    }
570
571    fn input(&mut self, event: &WindowEvent) -> bool {
572        self.input_handle.process_input(event)
573    }
574
575    fn resize(&mut self, new_size: Vec2<u32>) {
576        let context = self
577            .context
578            .as_mut()
579            .expect("Context hasnt been created yet run inside impl Game");
580
581        log::info!("RESZING");
582
583        if new_size.x > 0 && new_size.y > 0 {
584            context.config.width = new_size.x;
585            context.config.height = new_size.y;
586            context
587                .surface
588                .configure(&context.wgpu.device, &context.config);
589            self.size = new_size;
590            context.wgpu.queue.write_buffer(
591                &context.camera_buffer,
592                48,
593                bytemuck::cast_slice(&[new_size.x as f32, new_size.y as f32]),
594            );
595        }
596    }
597
598    fn handle_resource(&mut self, resource: Result<Resource, ResourceError>) {
599        match resource {
600            Ok(data) => {
601                self.loader.remove_item_loading(data.loading_op);
602                match data.resource_type {
603                    ResourceType::Bytes => self.add_finished_bytes(data.data, data.id, &data.path),
604                    ResourceType::Image(mag, min) => {
605                        self.add_finished_image(data.data, data.id, mag, min, &data.path)
606                    }
607                    ResourceType::Shader(options) => {
608                        self.add_finished_shader(data.data, data.id, options, &data.path)
609                    }
610                    ResourceType::Font => self.add_finished_font(data),
611                }
612            }
613            Err(e) => {
614                log::error!(
615                    "could not load resource: {:?}, becuase: {:?}, loading defualt replacement",
616                    e,
617                    e.error
618                );
619
620                self.loader.remove_item_loading(e.loading_op);
621
622                match e.resource_type {
623                    ResourceType::Bytes => self.add_defualt_bytes(e.id),
624                    ResourceType::Image(..) => self.add_defualt_image(e.id),
625                    ResourceType::Shader(_) => self.add_defualt_shader(e.id),
626                    ResourceType::Font => self.add_defualt_font(e.id),
627                }
628            }
629        }
630    }
631
632    fn add_finished_bytes(&mut self, data: Vec<u8>, id: NonZeroU64, path: &Path) {
633        let typed_id: ResourceId<Vec<u8>> = ResourceId::from_number(id);
634        self.resource_manager.insert_bytes(typed_id, data);
635        log::info!("byte resource at: {:?} loaded succesfully", path);
636    }
637
638    fn add_finished_image(
639        &mut self,
640        data: Vec<u8>,
641        id: NonZeroU64,
642        mag: SamplerType,
643        min: SamplerType,
644        path: &Path,
645    ) {
646        let typed_id: ResourceId<Texture> = ResourceId::from_number(id);
647        let texture = Texture::from_resource_data(self, None, data, mag, min);
648        match texture {
649            Ok(texture) => {
650                self.resource_manager.insert_texture(typed_id, texture);
651                log::info!("texture resource at: {:?} loaded succesfully", path);
652            }
653            Err(e) => log::error!("{:?}, loading defualt replacement", e),
654        }
655    }
656
657    fn add_finished_shader(
658        &mut self,
659        data: Vec<u8>,
660        id: NonZeroU64,
661        options: IntermediateOptions,
662        path: &Path,
663    ) {
664        let final_option =
665            FinalShaderOptions::from_intermediate(options, self.context.as_ref().unwrap());
666
667        let typed_id: ResourceId<Shader> = ResourceId::from_number(id);
668        let shader = Shader::from_resource_data(&data, final_option, self);
669        match shader {
670            Ok(shader) => {
671                self.resource_manager.insert_pipeline(typed_id, shader);
672                log::info!("shader resource at: {:?} loaded succesfully", path);
673            }
674            Err(e) => {
675                log::error!("{:?}. loading defualt replacement", e);
676                self.add_defualt_shader(id);
677            }
678        }
679    }
680
681    fn add_finished_font(&mut self, resource: Resource) {
682        let typed_id: ResourceId<Font> = ResourceId::from_number(resource.id);
683
684        let context = self.context.as_mut().unwrap();
685
686        let font = context.text_renderer.load_font_from_bytes(&resource.data);
687        self.resource_manager.insert_font(typed_id, font);
688        log::info!("Font resource at: {:?} loaded succesfully", resource.path);
689    }
690
691    fn add_defualt_bytes(&mut self, id: NonZeroU64) {
692        let typed_id: ResourceId<Vec<u8>> = ResourceId::from_number(id);
693        self.resource_manager.insert_bytes(typed_id, Vec::new());
694    }
695
696    fn add_defualt_image(&mut self, id: NonZeroU64) {
697        let typed_id: ResourceId<Texture> = ResourceId::from_number(id);
698        let image = Texture::default(self);
699        self.resource_manager.insert_texture(typed_id, image);
700    }
701
702    fn add_defualt_shader(&mut self, id: NonZeroU64) {
703        let context = self
704            .context
705            .as_ref()
706            .expect("Context hasnt been created yet run inside impl Game");
707
708        let typed_id: ResourceId<Shader> = ResourceId::from_number(id);
709        let shader = Shader::defualt(&context.wgpu, context.get_texture_format());
710        self.resource_manager.insert_pipeline(typed_id, shader);
711    }
712
713    fn add_defualt_font(&mut self, id: NonZeroU64) {
714        let context = self
715            .context
716            .as_ref()
717            .expect("Context hasnt been created yet run inside impl Game");
718
719        let typed_id: ResourceId<Font> = ResourceId::from_number(id);
720        let font = Font::from_str(context.text_renderer.get_defualt_font_name());
721        self.resource_manager.insert_font(typed_id, font);
722    }
723
724    pub(crate) fn is_loading(&self) -> bool {
725        // self.loader.get_loading_resources() > 0
726        #[cfg(not(target_arch = "wasm32"))]
727        {
728            false
729        }
730
731        #[cfg(target_arch = "wasm32")]
732        {
733            self.loader.is_blocked()
734        }
735    }
736}
737
738impl<T: Game> ApplicationHandler<BpEvent> for (Engine, T) {
739    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
740        let (engine, _) = self;
741
742        if engine.context.is_none() {
743            engine.context = Some(GraphicsContext::from_active_loop(
744                event_loop,
745                engine.window_options.take().unwrap(),
746                &mut engine.resource_manager,
747                &engine.defualt_resources,
748            ))
749        }
750    }
751
752    fn window_event(
753        &mut self,
754        event_loop: &ActiveEventLoop,
755        window_id: winit::window::WindowId,
756        event: WindowEvent,
757    ) {
758        let (engine, game) = self;
759        if window_id == engine.context.as_ref().unwrap().window.id() && !engine.input(&event) {
760            match event {
761                WindowEvent::CloseRequested => event_loop.exit(),
762                WindowEvent::Resized(physical_size) => {
763                    engine.resize(physical_size.into());
764                    game.on_resize(physical_size.into(), engine);
765                }
766                WindowEvent::RedrawRequested => {
767                    if engine.is_loading() {
768                        engine.update(event_loop);
769                    } else {
770                        game.update(engine);
771                        engine.update(event_loop);
772                        engine.current_frametime = Instant::now();
773
774                        match render(game, engine) {
775                            Ok(_) => {}
776                            // reconfigure surface if lost
777                            Err(wgpu::SurfaceError::Lost) => engine.resize(engine.size),
778                            Err(wgpu::SurfaceError::OutOfMemory) => {
779                                event_loop.exit();
780                            }
781                            Err(e) => eprintln!("{:?}", e),
782                        }
783                    }
784                }
785                e => {
786                    log::info!("{:?}", e);
787                }
788            }
789        }
790    }
791
792    fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: BpEvent) {
793        let (engine, _) = self;
794        match event {
795            BpEvent::ResourceLoaded(resource) => engine.handle_resource(resource),
796        }
797    }
798
799    fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
800        let (engine, _) = self;
801        engine.context.as_ref().unwrap().window.request_redraw();
802    }
803}
804
805/// A builder class that helps create an application
806/// with specific details
807pub struct EngineBuilder {
808    pub(crate) resolution: (u32, u32),
809    pub(crate) full_screen: bool,
810    target_fps: Option<u16>,
811    close_key: Option<Key>,
812    pub(crate) window_icon: Option<winit::window::Icon>,
813    pub(crate) window_title: String,
814    pub(crate) resizable: bool,
815    pub(crate) vsync: wgpu::PresentMode,
816}
817
818impl EngineBuilder {
819    /// Creates a builder with some defualt presets
820    /// ```rust
821    /// Self {
822    ///     resolution: (600, 600),
823    ///     full_screen: false,
824    ///     target_fps: 30,
825    ///     close_key: None,
826    ///     window_icon: None,
827    ///     window_title: "Bottonless-Pit Game".into(),
828    ///     resizable: true,
829    ///     vysnc: false,
830    /// }
831    pub fn new() -> Self {
832        Self {
833            resolution: (600, 600),
834            full_screen: false,
835            target_fps: None,
836            close_key: None,
837            window_icon: None,
838            window_title: "Bottomless-Pit Game".into(),
839            resizable: true,
840            vsync: wgpu::PresentMode::AutoVsync,
841        }
842    }
843
844    /// Overides the defualt resolution
845    pub fn with_resolution(self, resolution: (u32, u32)) -> Self {
846        Self {
847            resolution,
848            full_screen: self.full_screen,
849            target_fps: self.target_fps,
850            close_key: self.close_key,
851            window_icon: self.window_icon,
852            window_title: self.window_title,
853            resizable: self.resizable,
854            vsync: self.vsync,
855        }
856    }
857
858    /// Will cause the window to be fullscreen upon launch
859    pub fn fullscreen(self) -> Self {
860        Self {
861            resolution: self.resolution,
862            full_screen: true,
863            target_fps: self.target_fps,
864            close_key: self.close_key,
865            window_icon: self.window_icon,
866            window_title: self.window_title,
867            resizable: self.resizable,
868            vsync: self.vsync,
869        }
870    }
871
872    /// Sets a target fps cap. The thread will spin sleep using the
873    /// [spin_sleep](https://docs.rs/spin_sleep/latest/spin_sleep/index.html) crate
874    /// untill the desired frame time is reached. If the frames are slower than the target
875    /// no action is taken to "speed" up the rendering and updating
876    pub fn set_target_fps(self, fps: u16) -> Self {
877        Self {
878            resolution: self.resolution,
879            full_screen: self.full_screen,
880            target_fps: Some(fps),
881            close_key: self.close_key,
882            window_icon: self.window_icon,
883            window_title: self.window_title,
884            resizable: self.resizable,
885            vsync: self.vsync,
886        }
887    }
888
889    /// Will cause the framerate to be uncapped if the platform supports it using
890    /// wgpu's [PresentMode::AutoNoVsync](https://docs.rs/wgpu/latest/wgpu/enum.PresentMode.html)
891    /// by defualt the engine uses
892    /// [PresentMode::AutoVsync](https://docs.rs/wgpu/latest/wgpu/enum.PresentMode.html)
893    pub fn remove_vsync(self) -> Self {
894        Self {
895            resolution: self.resolution,
896            full_screen: self.full_screen,
897            target_fps: self.target_fps,
898            close_key: self.close_key,
899            window_icon: self.window_icon,
900            window_title: self.window_title,
901            resizable: self.resizable,
902            vsync: wgpu::PresentMode::AutoNoVsync,
903        }
904    }
905
906    /// Sets a key that will instantly close the window
907    pub fn set_close_key(self, key: Key) -> Self {
908        Self {
909            resolution: self.resolution,
910            full_screen: self.full_screen,
911            target_fps: self.target_fps,
912            close_key: Some(key),
913            window_icon: self.window_icon,
914            window_title: self.window_title,
915            resizable: self.resizable,
916            vsync: self.vsync,
917        }
918    }
919
920    /// Sets the title of the window
921    pub fn set_window_title(self, title: &str) -> Self {
922        Self {
923            resolution: self.resolution,
924            full_screen: self.full_screen,
925            target_fps: self.target_fps,
926            close_key: self.close_key,
927            window_icon: self.window_icon,
928            window_title: title.into(),
929            resizable: self.resizable,
930            vsync: self.vsync,
931        }
932    }
933
934    /// Sets the window icon
935    pub fn set_window_icon(self, icon: winit::window::Icon) -> Self {
936        Self {
937            resolution: self.resolution,
938            full_screen: self.full_screen,
939            target_fps: self.target_fps,
940            close_key: self.close_key,
941            window_icon: Some(icon),
942            window_title: self.window_title,
943            resizable: self.resizable,
944            vsync: self.vsync,
945        }
946    }
947
948    /// Prevents the window from being resized during runtime
949    pub fn unresizable(self) -> Self {
950        Self {
951            resolution: self.resolution,
952            full_screen: self.full_screen,
953            target_fps: self.target_fps,
954            close_key: self.close_key,
955            window_icon: self.window_icon,
956            window_title: self.window_title,
957            resizable: false,
958            vsync: self.vsync,
959        }
960    }
961
962    /// Attempts to buld the Engine
963    pub fn build(self) -> Result<Engine, BuildError> {
964        Engine::new(self)
965    }
966}
967
968impl Default for EngineBuilder {
969    fn default() -> Self {
970        Self::new()
971    }
972}
973
974/// All the errors that can occur when creating core engine resources
975#[derive(Debug)]
976pub enum BuildError {
977    /// Occurs when the operating system cannot do certain actions,
978    /// typically means the window could not be created.
979    WindowOsError(OsError),
980    /// Happens when the surface cannont be created. This can happen
981    /// on certian OS's or when the browser does not suport WebGL
982    CreateSurfaceError(CreateSurfaceError),
983    /// Occurs when the WGPU adapter cannot be found.
984    FailedToCreateAdapter,
985    /// Occurs when the WGPU device cannot be made. This usually
986    /// means the OS does not support the minimum graphics features.
987    RequestDeviceError(RequestDeviceError),
988    #[cfg(target_arch = "wasm32")]
989    /// This occurs when the code cannot fetch the JavaScript Window element.
990    CantGetWebWindow,
991    #[cfg(target_arch = "wasm32")]
992    /// This occurs when the Document element cannout be found.
993    CantGetDocument,
994    #[cfg(target_arch = "wasm32")]
995    /// This is any error that can come from the calling of JavaScript
996    /// APIs.
997    CantGetBody,
998    #[cfg(target_arch = "wasm32")]
999    JsError(wasm_bindgen::JsValue),
1000}
1001
1002impl std::fmt::Display for BuildError {
1003    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1004        match self {
1005            Self::WindowOsError(e) => write!(f, "{}", e),
1006            Self::CreateSurfaceError(e) => write!(f, "{}", e),
1007            Self::FailedToCreateAdapter => write!(f, "unable to create Adapater"),
1008            Self::RequestDeviceError(e) => write!(f, "{}", e),
1009            #[cfg(target_arch = "wasm32")]
1010            Self::CantGetWebWindow => write!(f, "could not get web Window"),
1011            #[cfg(target_arch = "wasm32")]
1012            Self::CantGetDocument => write!(f, "could not get HTML document"),
1013            #[cfg(target_arch = "wasm32")]
1014            Self::CantGetBody => write!(f, "could nto get HTML body tag"),
1015            #[cfg(target_arch = "wasm32")]
1016            Self::JsError(e) => write!(f, "{:?}", e),
1017        }
1018    }
1019}
1020
1021impl std::error::Error for BuildError {}
1022
1023impl From<OsError> for BuildError {
1024    fn from(value: OsError) -> Self {
1025        Self::WindowOsError(value)
1026    }
1027}
1028
1029impl From<CreateSurfaceError> for BuildError {
1030    fn from(value: CreateSurfaceError) -> Self {
1031        Self::CreateSurfaceError(value)
1032    }
1033}
1034
1035impl From<RequestDeviceError> for BuildError {
1036    fn from(value: RequestDeviceError) -> Self {
1037        Self::RequestDeviceError(value)
1038    }
1039}
1040
1041#[cfg(target_arch = "wasm32")]
1042impl From<wasm_bindgen::JsValue> for BuildError {
1043    fn from(value: wasm_bindgen::JsValue) -> Self {
1044        Self::JsError(value)
1045    }
1046}
1047
1048/// Errors that can occur when setting the window Icon.
1049#[derive(Debug)]
1050pub enum IconError {
1051    /// Occurs because the image does not meet certain
1052    /// requirments please see [winit docs](https://docs.rs/winit/latest/winit/window/enum.BadIcon.html).
1053    BadIcon(BadIcon),
1054    /// The image file was not a valid image
1055    IconLoadingError(ImageError),
1056}
1057
1058impl From<BadIcon> for IconError {
1059    fn from(value: BadIcon) -> Self {
1060        Self::BadIcon(value)
1061    }
1062}
1063
1064impl From<ImageError> for IconError {
1065    fn from(value: ImageError) -> Self {
1066        Self::IconLoadingError(value)
1067    }
1068}
1069
1070impl std::fmt::Display for IconError {
1071    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1072        match self {
1073            Self::BadIcon(e) => write!(f, "{}", e),
1074            Self::IconLoadingError(e) => write!(f, "{}", e),
1075        }
1076    }
1077}
1078
1079impl std::error::Error for IconError {}
1080
1081pub(crate) struct DefualtResources {
1082    pub(crate) defualt_texture_id: ResourceId<Texture>,
1083    pub(crate) default_pipeline_id: ResourceId<Shader>,
1084    pub(crate) line_pipeline_id: ResourceId<Shader>,
1085}
1086
1087#[derive(Debug)]
1088pub(crate) enum BpEvent {
1089    ResourceLoaded(Result<Resource, ResourceError>),
1090}