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