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*/