1use crate::{
2 assets::{
3 font::FontAssetSubsystem, shader::ShaderAssetSubsystem, sound::SoundAssetSubsystem,
4 texture::TextureAssetSubsystem,
5 },
6 audio::Audio,
7 context::GameContext,
8};
9#[cfg(not(target_arch = "wasm32"))]
10use glutin::{event::Event, window::Window};
11#[cfg(target_arch = "wasm32")]
12use instant::Instant;
13use keket::database::AssetDatabase;
14use spitfire_draw::{
15 context::DrawContext,
16 utils::{ShaderRef, Vertex},
17};
18use spitfire_glow::{app::AppState, graphics::Graphics, renderer::GlowBlending};
19use spitfire_gui::context::GuiContext;
20use spitfire_input::InputContext;
21#[cfg(not(target_arch = "wasm32"))]
22use std::time::Instant;
23#[cfg(target_arch = "wasm32")]
24use winit::{event::Event, window::Window};
25
26pub trait GameObject {
27 #[allow(unused_variables)]
28 fn activate(&mut self, context: &mut GameContext) {}
29
30 #[allow(unused_variables)]
31 fn deactivate(&mut self, context: &mut GameContext) {}
32
33 #[allow(unused_variables)]
34 fn process(&mut self, context: &mut GameContext, delta_time: f32) {}
35
36 #[allow(unused_variables)]
37 fn draw(&mut self, context: &mut GameContext) {}
38}
39
40#[derive(Default)]
41pub enum GameStateChange {
42 #[default]
43 Continue,
44 Swap(Box<dyn GameState>),
45 Push(Box<dyn GameState>),
46 Pop,
47}
48
49#[allow(unused_variables)]
50pub trait GameState {
51 fn enter(&mut self, context: GameContext) {}
52
53 fn exit(&mut self, context: GameContext) {}
54
55 fn update(&mut self, context: GameContext, delta_time: f32) {}
56
57 fn fixed_update(&mut self, context: GameContext, delta_time: f32) {}
58
59 fn draw(&mut self, context: GameContext) {}
60
61 fn draw_gui(&mut self, context: GameContext) {}
62}
63
64pub trait GameSubsystem {
65 fn run(&mut self, context: GameContext, delta_time: f32);
66}
67
68pub struct GameInstance {
69 pub fixed_delta_time: f32,
70 pub color_shader: &'static str,
71 pub image_shader: &'static str,
72 pub text_shader: &'static str,
73 pub input_maintain_on_fixed_step: bool,
74 draw: DrawContext,
75 gui: GuiContext,
76 input: InputContext,
77 assets: AssetDatabase,
78 audio: Audio,
79 timer: Instant,
80 fixed_timer: Instant,
81 states: Vec<Box<dyn GameState>>,
82 state_change: GameStateChange,
83 subsystems: Vec<Box<dyn GameSubsystem>>,
84}
85
86impl Default for GameInstance {
87 fn default() -> Self {
88 Self {
89 fixed_delta_time: 1.0 / 60.0,
90 color_shader: "color",
91 image_shader: "image",
92 text_shader: "text",
93 input_maintain_on_fixed_step: true,
94 draw: Default::default(),
95 gui: Default::default(),
96 input: Default::default(),
97 assets: Default::default(),
98 audio: Default::default(),
99 timer: Instant::now(),
100 fixed_timer: Instant::now(),
101 states: Default::default(),
102 state_change: Default::default(),
103 subsystems: vec![
104 Box::new(ShaderAssetSubsystem),
105 Box::new(TextureAssetSubsystem),
106 Box::new(FontAssetSubsystem),
107 Box::new(SoundAssetSubsystem),
108 ],
109 }
110 }
111}
112
113impl GameInstance {
114 pub fn new(state: impl GameState + 'static) -> Self {
115 Self {
116 state_change: GameStateChange::Push(Box::new(state)),
117 ..Default::default()
118 }
119 }
120
121 pub fn with_fixed_time_step(mut self, value: f32) -> Self {
122 self.fixed_delta_time = value;
123 self
124 }
125
126 pub fn with_fps(mut self, frames_per_second: usize) -> Self {
127 self.set_fps(frames_per_second);
128 self
129 }
130
131 pub fn with_color_shader(mut self, name: &'static str) -> Self {
132 self.color_shader = name;
133 self
134 }
135
136 pub fn with_image_shader(mut self, name: &'static str) -> Self {
137 self.image_shader = name;
138 self
139 }
140
141 pub fn with_text_shader(mut self, name: &'static str) -> Self {
142 self.text_shader = name;
143 self
144 }
145
146 pub fn with_input_maintain_on_fixed_step(mut self, value: bool) -> Self {
147 self.input_maintain_on_fixed_step = value;
148 self
149 }
150
151 pub fn with_subsystem(mut self, subsystem: impl GameSubsystem + 'static) -> Self {
152 self.subsystems.push(Box::new(subsystem));
153 self
154 }
155
156 pub fn setup_assets(mut self, f: impl FnOnce(&mut AssetDatabase)) -> Self {
157 f(&mut self.assets);
158 self
159 }
160
161 pub fn fps(&self) -> usize {
162 (1.0 / self.fixed_delta_time).ceil() as usize
163 }
164
165 pub fn set_fps(&mut self, frames_per_second: usize) {
166 self.fixed_delta_time = 1.0 / frames_per_second as f32;
167 }
168
169 pub fn process_frame(&mut self, graphics: &mut Graphics<Vertex>) {
170 let delta_time = self.timer.elapsed().as_secs_f32();
171
172 for subsystem in &mut self.subsystems {
173 subsystem.run(
174 GameContext {
175 graphics,
176 draw: &mut self.draw,
177 gui: &mut self.gui,
178 input: &mut self.input,
179 state_change: &mut self.state_change,
180 assets: &mut self.assets,
181 audio: &mut self.audio,
182 },
183 delta_time,
184 );
185 }
186 self.assets.maintain().unwrap();
187
188 if let Some(state) = self.states.last_mut() {
189 self.timer = Instant::now();
190 state.update(
191 GameContext {
192 graphics,
193 draw: &mut self.draw,
194 gui: &mut self.gui,
195 input: &mut self.input,
196 state_change: &mut self.state_change,
197 assets: &mut self.assets,
198 audio: &mut self.audio,
199 },
200 delta_time,
201 );
202 }
203
204 let fixed_delta_time = self.fixed_timer.elapsed().as_secs_f32();
205 let fixed_step = if fixed_delta_time > self.fixed_delta_time {
206 self.fixed_timer = Instant::now();
207 if let Some(state) = self.states.last_mut() {
208 state.fixed_update(
209 GameContext {
210 graphics,
211 draw: &mut self.draw,
212 gui: &mut self.gui,
213 input: &mut self.input,
214 state_change: &mut self.state_change,
215 assets: &mut self.assets,
216 audio: &mut self.audio,
217 },
218 fixed_delta_time,
219 );
220 }
221 true
222 } else {
223 false
224 };
225
226 self.draw.begin_frame(graphics);
227 self.draw.push_shader(&ShaderRef::name(self.image_shader));
228 self.draw.push_blending(GlowBlending::Alpha);
229 if let Some(state) = self.states.last_mut() {
230 state.draw(GameContext {
231 graphics,
232 draw: &mut self.draw,
233 gui: &mut self.gui,
234 input: &mut self.input,
235 state_change: &mut self.state_change,
236 assets: &mut self.assets,
237 audio: &mut self.audio,
238 });
239 }
240 self.gui.begin_frame();
241 if let Some(state) = self.states.last_mut() {
242 state.draw_gui(GameContext {
243 graphics,
244 draw: &mut self.draw,
245 gui: &mut self.gui,
246 input: &mut self.input,
247 state_change: &mut self.state_change,
248 assets: &mut self.assets,
249 audio: &mut self.audio,
250 });
251 }
252 self.gui.end_frame(
253 &mut self.draw,
254 graphics,
255 &ShaderRef::name(self.color_shader),
256 &ShaderRef::name(self.image_shader),
257 &ShaderRef::name(self.text_shader),
258 );
259 self.draw.end_frame();
260 if !self.input_maintain_on_fixed_step || fixed_step {
261 self.input.maintain();
262 }
263
264 match std::mem::take(&mut self.state_change) {
265 GameStateChange::Continue => {}
266 GameStateChange::Swap(mut state) => {
267 if let Some(mut state) = self.states.pop() {
268 state.exit(GameContext {
269 graphics,
270 draw: &mut self.draw,
271 gui: &mut self.gui,
272 input: &mut self.input,
273 state_change: &mut self.state_change,
274 assets: &mut self.assets,
275 audio: &mut self.audio,
276 });
277 }
278 state.enter(GameContext {
279 graphics,
280 draw: &mut self.draw,
281 gui: &mut self.gui,
282 input: &mut self.input,
283 state_change: &mut self.state_change,
284 assets: &mut self.assets,
285 audio: &mut self.audio,
286 });
287 self.states.push(state);
288 self.timer = Instant::now();
289 }
290 GameStateChange::Push(mut state) => {
291 state.enter(GameContext {
292 graphics,
293 draw: &mut self.draw,
294 gui: &mut self.gui,
295 input: &mut self.input,
296 state_change: &mut self.state_change,
297 assets: &mut self.assets,
298 audio: &mut self.audio,
299 });
300 self.states.push(state);
301 self.timer = Instant::now();
302 }
303 GameStateChange::Pop => {
304 if let Some(mut state) = self.states.pop() {
305 state.exit(GameContext {
306 graphics,
307 draw: &mut self.draw,
308 gui: &mut self.gui,
309 input: &mut self.input,
310 state_change: &mut self.state_change,
311 assets: &mut self.assets,
312 audio: &mut self.audio,
313 });
314 }
315 self.timer = Instant::now();
316 }
317 }
318 }
319
320 pub fn process_event(&mut self, event: &Event<()>) -> bool {
321 if let Event::WindowEvent { event, .. } = event {
322 self.input.on_event(event);
323 }
324 !self.states.is_empty() || !matches!(self.state_change, GameStateChange::Continue)
325 }
326}
327
328impl AppState<Vertex> for GameInstance {
329 fn on_redraw(&mut self, graphics: &mut Graphics<Vertex>) {
330 self.process_frame(graphics);
331 }
332
333 fn on_event(&mut self, event: Event<()>, _: &mut Window) -> bool {
334 self.process_event(&event)
335 }
336}