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