swamp_game_wgpu/
lib.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/swamp/swamp
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5pub mod prelude;
6
7use crate::prelude::Glyph;
8use int_math::{URect, UVec2};
9use monotonic_time_rs::{InstantMonotonicClock, Millis, MonotonicClock};
10use std::cmp::{max, min};
11use std::fmt::{Debug, Formatter};
12use std::marker::PhantomData;
13use swamp_app::prelude::{
14    App, AppReturnValue, ApplicationExit, Msg, Plugin, Re, ReM, ResourceStorage, UpdatePhase,
15};
16use swamp_app::prelude::{MessagesIterator, Resource};
17use swamp_app::system_types::ReAll;
18use swamp_asset_registry::AssetRegistry;
19use swamp_assets::prelude::{AssetName, Id};
20pub use swamp_basic_input::prelude::*;
21use swamp_game::{Application, Assets};
22use swamp_render_wgpu::prelude::Font;
23use swamp_render_wgpu::{FixedAtlas, FontAndMaterial, MaterialRef};
24use swamp_render_wgpu::{Material, Render};
25use swamp_screen::WindowMessage;
26use swamp_wgpu_window::WgpuWindow;
27use tracing::debug;
28
29#[derive(Debug, Resource)]
30pub struct GameWgpuSettings {
31    pub virtual_size: UVec2,
32}
33
34pub struct WgpuAssets<'a> {
35    resource_storage: &'a mut ResourceStorage,
36    clock: InstantMonotonicClock,
37}
38
39impl<'a> WgpuAssets<'a> {
40    pub fn new(resource_storage: &'a mut ResourceStorage) -> Self {
41        Self {
42            resource_storage,
43            clock: InstantMonotonicClock::new(),
44        }
45    }
46}
47
48impl<'a> Assets for WgpuAssets<'a> {
49    fn material_png(&mut self, name: impl Into<AssetName>) -> MaterialRef {
50        let asset_loader = self
51            .resource_storage
52            .get_mut::<AssetRegistry>()
53            .expect("should exist registry");
54        asset_loader.load::<Material>(name.into().with_extension("png"))
55    }
56
57    fn frame_fixed_grid_material_png(
58        &mut self,
59        name: impl Into<AssetName>,
60        grid_size: UVec2,
61        texture_size: UVec2,
62    ) -> FixedAtlas {
63        let material_ref = self.material_png(name);
64
65        FixedAtlas::new(grid_size, texture_size, material_ref)
66    }
67
68    fn bm_font(&mut self, name: impl Into<AssetName>) -> FontAndMaterial {
69        let asset_name = name.into();
70        let asset_loader = self
71            .resource_storage
72            .get_mut::<AssetRegistry>()
73            .expect("should exist registry");
74        let font_ref = asset_loader.load::<Font>(asset_name.clone().with_extension("fnt"));
75        let material_ref = asset_loader.load::<Material>(asset_name.clone().with_extension("png"));
76
77        FontAndMaterial {
78            font_ref,
79            material_ref,
80        }
81    }
82
83    fn font(&self, font_ref: &Id<Font>) -> Option<&Font> {
84        let font_assets = self
85            .resource_storage
86            .get::<swamp_assets::Assets<Font>>()
87            .expect("font assets should be a thing");
88
89        font_assets.get(font_ref)
90    }
91
92    fn text_glyphs(&self, text: &str, font_and_mat: &FontAndMaterial) -> Option<Vec<Glyph>> {
93        if let Some(font) = self.font(&font_and_mat.font_ref) {
94            let glyphs = font.draw(text);
95            Some(glyphs)
96        } else {
97            None
98        }
99    }
100
101    fn now(&self) -> Millis {
102        self.clock.now()
103    }
104}
105
106#[derive(Resource)]
107pub struct WgpuGame<G: Application> {
108    game: G,
109}
110
111impl<G: Application> Debug for WgpuGame<G> {
112    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
113        write!(f, "WgpuGame")
114    }
115}
116
117impl<G: Application> WgpuGame<G> {
118    #[must_use]
119    pub fn new(assets: &mut impl Assets) -> Self {
120        Self {
121            game: G::new(assets),
122        }
123    }
124
125    pub fn inputs(&mut self, iter: MessagesIterator<InputMessage>) {
126        for message in iter {
127            match message {
128                InputMessage::KeyboardInput(button_state, key_code) => {
129                    self.game.keyboard_input(*button_state, *key_code)
130                }
131                InputMessage::MouseInput(button_state, button) => {
132                    self.game.mouse_input(*button_state, *button);
133                }
134                InputMessage::MouseWheel(scroll_delta, _touch_phase) => {
135                    if let MouseScrollDelta::LineDelta(delta) = scroll_delta {
136                        let game_scroll_y = (-delta.y as f32 * 120.0) as i16;
137                        self.game.mouse_wheel(game_scroll_y);
138                    }
139                }
140            }
141        }
142    }
143
144    /*
145    fn cursor_moved(&mut self, physical_position: UVec2) {
146        let viewport = info.main_render.viewport();
147
148        let relative_x = max(
149            0,
150            min(
151                physical_position.x as i64 - viewport.position.x as i64,
152                (viewport.size.x - 1) as i64,
153            ),
154        );
155
156        let relative_y = max(
157            0,
158            min(
159                physical_position.y as i64 - viewport.position.y as i64,
160                (viewport.size.y - 1) as i64,
161            ),
162        );
163
164        let clamped_to_viewport: UVec2 =
165            UVec2::new(relative_x as u16, (viewport.size.y - 1) - relative_y as u16);
166        let virtual_position_x = (clamped_to_viewport.x as u64
167            * info.main_render.virtual_surface_size().x as u64)
168            / viewport.size.x as u64;
169        let virtual_position_y = (clamped_to_viewport.y as u64
170            * info.main_render.virtual_surface_size().y as u64)
171            / viewport.size.y as u64;
172
173        let virtual_position = UVec2::new(virtual_position_x as u16, virtual_position_y as u16);
174        self.cursor_moved_delayed = Some(virtual_position);
175    }
176
177     */
178
179    pub fn cursor_moved(
180        &mut self,
181        physical_position: UVec2,
182        viewport: URect,
183        virtual_surface_size: UVec2,
184    ) {
185        let relative_x = max(
186            0,
187            min(
188                physical_position.x as i64 - viewport.position.x as i64,
189                (viewport.size.x - 1) as i64,
190            ),
191        );
192
193        let relative_y = max(
194            0,
195            min(
196                physical_position.y as i64 - viewport.position.y as i64,
197                (viewport.size.y - 1) as i64,
198            ),
199        );
200
201        let clamped_to_viewport: UVec2 = UVec2::new(relative_x as u16, relative_y as u16);
202
203        let virtual_position_x =
204            (clamped_to_viewport.x as u64 * virtual_surface_size.x as u64) / viewport.size.x as u64;
205
206        let virtual_position_y =
207            (clamped_to_viewport.y as u64 * virtual_surface_size.y as u64) / viewport.size.y as u64;
208
209        let virtual_position = UVec2::new(virtual_position_x as u16, virtual_position_y as u16);
210        self.game.cursor_moved(virtual_position)
211    }
212
213    pub fn mouse_move(&mut self, iter: MessagesIterator<WindowMessage>, wgpu_render: &Render) {
214        for message in iter {
215            match message {
216                WindowMessage::CursorMoved(position) => self.cursor_moved(
217                    *position,
218                    wgpu_render.viewport(),
219                    wgpu_render.virtual_surface_size(),
220                ),
221                WindowMessage::WindowCreated() => {}
222                WindowMessage::Resized(_) => {}
223            }
224        }
225    }
226
227    pub fn tick(&mut self, assets: &mut impl Assets) {
228        self.game.tick(assets);
229    }
230
231    pub fn render(
232        &mut self,
233        wgpu: &WgpuWindow,
234        wgpu_render: &mut Render,
235        materials: &swamp_assets::Assets<Material>,
236        fonts: &swamp_assets::Assets<Font>,
237    ) {
238        self.game.render(wgpu_render);
239
240        wgpu.render(wgpu_render.clear_color(), |render_pass| {
241            wgpu_render.render(render_pass, materials, fonts)
242        })
243        .unwrap();
244    }
245}
246
247pub struct GameWgpuPlugin<G: Application> {
248    pub phantom_data: PhantomData<G>,
249}
250impl<G: Application> Default for GameWgpuPlugin<G> {
251    fn default() -> Self {
252        Self::new()
253    }
254}
255
256impl<G: Application> GameWgpuPlugin<G> {
257    pub const fn new() -> Self {
258        Self {
259            phantom_data: PhantomData,
260        }
261    }
262}
263
264// TODO: add support for having tuple arguments to have maximum seven parameters
265#[allow(clippy::too_many_arguments)]
266pub fn tick<G: Application>(
267    window: Re<WgpuWindow>,
268    mut wgpu_render: ReM<Render>,
269    materials: Re<swamp_assets::Assets<Material>>,
270    fonts: Re<swamp_assets::Assets<Font>>,
271    input_messages: Msg<InputMessage>,
272    window_messages: Msg<WindowMessage>,
273    mut all_resources: ReAll,
274    mut internal_game: ReM<WgpuGame<G>>,
275) {
276    internal_game.inputs(input_messages.iter_previous());
277    internal_game.mouse_move(window_messages.iter_previous(), &wgpu_render);
278
279    let mut asset = WgpuAssets::new(&mut all_resources);
280    internal_game.tick(&mut asset);
281    if internal_game.game.wants_to_quit() {
282        all_resources.insert(ApplicationExit {
283            value: AppReturnValue::Value(0),
284        });
285    }
286    internal_game.render(&window, &mut wgpu_render, &materials, &fonts);
287}
288
289impl<G: Application> Plugin for GameWgpuPlugin<G> {
290    fn post_initialization(&self, app: &mut App) {
291        let storage = app.resources_mut();
292
293        let mut asset = WgpuAssets::new(storage);
294
295        debug!("calling WgpuGame::new()");
296        let internal_game = WgpuGame::<G>::new(&mut asset);
297        app.insert_resource(internal_game);
298
299        app.add_system(UpdatePhase::Update, tick::<G>);
300    }
301}
302
303/*
304impl<T: Application> ApplicationWrap<T> {
305    pub fn run(title: &str, virtual_surface_size: UVec2, suggested_physical_surface_size: UVec2) {
306        let mut app = Self {
307            app: None,
308            virtual_surface_size,
309            suggested_physical_surface_size,
310            cursor_moved_delayed: None,
311            wgpu_window: None,
312        };
313
314        let _ = swamp_window::WindowRunner::run_app(&mut app, title);
315    }
316
317    fn after(&mut self, wgpu_window: WgpuWindow) {
318        let asset_reader = swamp_assets::get_platform_reader("assets/".to_string());
319        // let physical_size = window.inner_size();
320        let physical_size = PhysicalSize::new(10, 10);
321        let mut render = Render::new(
322            Arc::clone(wgpu_window.device()),
323            Arc::clone(wgpu_window.queue()),
324            wgpu_window.surface_config().format,
325            (physical_size.width as u16, physical_size.height as u16).into(),
326            self.virtual_surface_size,
327            asset_reader,
328        );
329
330        let custom_app = T::new(&mut render);
331
332        self.app = Some(AppInfo {
333            window: wgpu_window,
334            main_render: render,
335            app: custom_app,
336        });
337    }
338}
339
340pub struct AppInfo<T: Application> {
341    window: WgpuWindow,
342    main_render: Render,
343    app: T,
344}
345
346pub struct ApplicationWrap<T: Application> {
347    app: Option<AppInfo<T>>,
348    virtual_surface_size: UVec2,
349    suggested_physical_surface_size: UVec2,
350    cursor_moved_delayed: Option<UVec2>,
351    wgpu_window: Option<WgpuWindow>,
352}
353
354impl<T: Application> AppHandler for ApplicationWrap<T> {
355    fn min_size(&self) -> (u16, u16) {
356        (self.virtual_surface_size.x, self.virtual_surface_size.y)
357    }
358
359    fn start_size(&self) -> (u16, u16) {
360        (
361            self.suggested_physical_surface_size.x,
362            self.suggested_physical_surface_size.y,
363        )
364    }
365
366    fn cursor_should_be_visible(&self) -> bool {
367        self.app
368            .as_ref()
369            .map_or(true, |info| info.app.wants_cursor_visible())
370    }
371
372    fn redraw(&mut self) -> bool {
373        if let Some(ref mut info) = self.app {
374            if let Some(cursor_move_delayed) = self.cursor_moved_delayed {
375                info.app.cursor_moved(cursor_move_delayed);
376                self.cursor_moved_delayed = None;
377            }
378            info.app.tick(); // TODO: Fix a better tick rate
379            if info.app.wants_to_quit() {
380                return false;
381            }
382
383            info.app.render(&mut info.main_render);
384            info.window
385                .render(info.main_render.clear_color(), |render_pass| {
386                    info.main_render.render(render_pass)
387                })
388                .expect("TODO: panic message");
389        }
390
391        true
392    }
393
394    fn got_focus(&mut self) {}
395
396    fn lost_focus(&mut self) {}
397
398    fn window_created(&mut self, window: Arc<Window>) {
399        trace!("create window, boot up");
400    }
401
402    fn resized(&mut self, physical_size: dpi::PhysicalSize<u32>) {
403        trace!("window resized (physical_size: {:?})", physical_size);
404        if let Some(ref mut info) = self.app {
405            info.window.resize(physical_size);
406            info.main_render
407                .resize((physical_size.width as u16, physical_size.height as u16).into());
408        }
409    }
410
411    fn keyboard_input(&mut self, element_state: ElementState, physical_key: PhysicalKey) {
412        if let Some(ref mut info) = self.app {
413            if let PhysicalKey::Code(key_code) = physical_key {
414                if let Ok(keycode) = key_code.try_into() {
415                    info.app.keyboard_input(element_state.into(), keycode);
416                }
417            }
418        }
419    }
420
421    fn cursor_entered(&mut self) {
422        if let Some(ref mut info) = self.app {
423            info.app.cursor_entered();
424        }
425    }
426
427    fn cursor_left(&mut self) {
428        if let Some(ref mut info) = self.app {
429            info.app.cursor_left();
430        }
431    }
432
433    fn cursor_moved(&mut self, physical_position: PhysicalPosition<f64>) {
434        if let Some(ref mut info) = self.app {
435            self.cursor_moved_delayed = Some(virtual_position);
436        }
437    }
438
439    fn mouse_input(&mut self, element_state: ElementState, button: MouseButton) {
440        if let Some(ref mut info) = self.app {
441            if let Ok(converted_button) = button.try_into() {
442                info.app.mouse_input(element_state.into(), converted_button);
443            }
444        }
445    }
446
447    fn mouse_wheel(&mut self, delta: MouseScrollDelta, _touch_phase: TouchPhase) {
448        if let Some(ref mut info) = self.app {
449            if let MouseScrollDelta::LineDelta(.., y) = delta {
450                info.app.mouse_wheel((-y * 120.0) as i16);
451            }
452        }
453    }
454
455    fn mouse_motion(&mut self, delta: (f64, f64)) {
456        if let Some(ref mut info) = self.app {
457            let factor = 65.0;
458            let converted = Vec2::new((delta.0 * factor) as i16, (-delta.1 * factor) as i16);
459            info.app.mouse_motion(converted);
460        }
461    }
462
463    fn touch(&mut self, _touch: Touch) {
464        // TODO:
465    }
466
467    fn scale_factor_changed(&mut self, scale_factor: f64, mut inner_size_writer: InnerSizeWriter) {
468        if let Some(ref mut info) = self.app {
469            if let Some(new_inner) = info.app.scale_factor_changed(scale_factor) {
470                let physical_size = PhysicalSize::new(new_inner.x as u32, new_inner.y as u32);
471                inner_size_writer.request_inner_size(physical_size).unwrap();
472            }
473        }
474    }
475}
476*/