1use std::time::Duration;
2
3use anyhow::Result;
4use winit::application::ApplicationHandler;
5use winit::event::WindowEvent;
6use winit::event_loop::{ActiveEventLoop, EventLoop};
7use winit::window::WindowId;
8
9use crate::Game;
10use crate::context::{Context, GameEvent};
11
12#[derive(Clone)]
13pub struct AppConfig {
14 pub title: String,
15 pub width: u32,
16 pub height: u32,
17 pub vsync: bool,
18 pub fixed_timestep: Option<Duration>,
20 pub asset_root: std::path::PathBuf,
22 pub depth_format: Option<wgpu::TextureFormat>,
26 pub msaa_samples: u32,
29}
30
31impl Default for AppConfig {
32 fn default() -> Self {
33 Self {
34 title: "Game".into(),
35 width: 1280,
36 height: 720,
37 vsync: true,
38 fixed_timestep: None,
39 asset_root: std::path::PathBuf::from("assets"),
40 depth_format: None,
41 msaa_samples: 1,
42 }
43 }
44}
45
46pub fn run<G: Game>(config: AppConfig) -> Result<()> {
47 let event_loop = EventLoop::new()?;
48 event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
49 let mut runner = AppRunner::<G> {
50 config,
51 state: None,
52 accumulator: Duration::ZERO,
53 };
54 event_loop.run_app(&mut runner)?;
55 Ok(())
56}
57
58struct AppRunner<G: Game> {
59 config: AppConfig,
60 state: Option<RunState<G>>,
61 accumulator: Duration,
62}
63
64struct RunState<G: Game> {
65 ctx: Context,
66 game: G,
67}
68
69impl<G: Game> ApplicationHandler for AppRunner<G> {
70 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
71 if self.state.is_some() {
72 return;
73 }
74 let mut ctx = match Context::new(
75 event_loop,
76 &self.config.title,
77 self.config.width,
78 self.config.height,
79 self.config.vsync,
80 self.config.asset_root.clone(),
81 self.config.depth_format,
82 self.config.msaa_samples,
83 ) {
84 Ok(c) => c,
85 Err(e) => {
86 log::error!("context init failed: {e:?}");
87 event_loop.exit();
88 return;
89 }
90 };
91 let game = match G::init(&mut ctx) {
92 Ok(g) => g,
93 Err(e) => {
94 log::error!("game init failed: {e:?}");
95 event_loop.exit();
96 return;
97 }
98 };
99 ctx.window.request_redraw();
100 self.state = Some(RunState { ctx, game });
101 }
102
103 fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
104 let Some(state) = self.state.as_mut() else {
105 return;
106 };
107 let consumed = state.game.raw_window_event(&mut state.ctx, &event);
109 match event {
110 WindowEvent::CloseRequested => {
111 state.game.event(&mut state.ctx, &GameEvent::CloseRequested);
112 event_loop.exit();
113 }
114 WindowEvent::Resized(size) => {
115 state.ctx.gfx.resize(size.width, size.height);
116 state.game.event(
117 &mut state.ctx,
118 &GameEvent::Resized {
119 width: size.width,
120 height: size.height,
121 },
122 );
123 }
124 WindowEvent::Focused(focused) => {
125 state
126 .ctx
127 .input
128 .handle_window_event(&WindowEvent::Focused(focused));
129 state
130 .game
131 .event(&mut state.ctx, &GameEvent::FocusChanged(focused));
132 }
133 WindowEvent::RedrawRequested => {
134 self.frame(event_loop);
135 }
136 ref other if !consumed => {
137 state.ctx.input.handle_window_event(other);
138 }
139 _ => {}
140 }
141 }
142
143 fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
144 if let Some(state) = self.state.as_ref() {
145 state.ctx.window.request_redraw();
146 }
147 }
148
149 fn exiting(&mut self, _event_loop: &ActiveEventLoop) {
150 if let Some(state) = self.state.as_mut() {
151 state.game.shutdown(&mut state.ctx);
152 }
153 }
154}
155
156impl<G: Game> AppRunner<G> {
157 fn frame(&mut self, event_loop: &ActiveEventLoop) {
158 let Some(state) = self.state.as_mut() else {
159 return;
160 };
161 state.ctx.time.tick();
162 state.ctx.input.poll_gamepads();
163
164 match self.config.fixed_timestep {
165 Some(step) => {
166 self.accumulator += state.ctx.time.delta;
167 while self.accumulator >= step {
168 state.game.update(&mut state.ctx, step.as_secs_f32());
169 self.accumulator -= step;
170 }
171 }
172 None => {
173 let dt = state.ctx.time.delta.as_secs_f32();
174 state.game.update(&mut state.ctx, dt);
175 }
176 }
177
178 if state.ctx.quit_requested {
179 event_loop.exit();
180 return;
181 }
182
183 state.ctx.input.end_frame();
184
185 match state.ctx.gfx.begin_frame() {
186 Ok(mut frame) => {
187 state.game.render(&mut state.ctx, &mut frame);
188 state.ctx.gfx.present(frame);
189 }
190 Err(e) => {
191 log::warn!("begin_frame failed: {e:?}");
192 let (w, h) = state.ctx.gfx.size();
193 state.ctx.gfx.resize(w, h);
194 }
195 }
196 }
197}